statinize 0.0.1 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51c0c480de402d268d6d85db85624f112019e8ee9456e7f32553dac8f42821d5
4
- data.tar.gz: fd177fd2e864c68370840a2c5cdbc0725b7eaa8eeff9358d2a14061176a16de5
3
+ metadata.gz: 7814eb5a73fe5fb67c846398c6d22573b183cafd88c243175ef4fb39c5f71fbe
4
+ data.tar.gz: b7eec7d141ae6a0deda224802f67dae791ea7b9c96957922c9a7fa9c1b052e6a
5
5
  SHA512:
6
- metadata.gz: a262a3f5f8afee169fab603fbc1530f4f84052d7786d47f3eb2bfbed4b3a2c939e15175e800a0aee1c9ce6f47cc0d4627395fe8c8c2862d40cc1d155754278b0
7
- data.tar.gz: 752cb938cef235da3efdf2da7be11b96a3319cab0ee6eda7f58c8a3326d9c8b33c7622d8da7163f9e71a5e9341eb679904e82ef77184de62a0c64033b00da528
6
+ metadata.gz: 8bea982ae518d67bb7f9c0a9cab47dd081d869dabbdf01874e14de5c2004217bd6f395e5e040f93499cbe5de413e1d3ed6b59222e9d8089cb6b01289748d1be9
7
+ data.tar.gz: '08111b888a705bb74fe044f9de93ebcfd6863646df65510bf62e0566b23af1e4ef410609a22b0fc1afb1b15ffac62edbd9b43264a02191504e25296a99eab1e8'
@@ -0,0 +1,35 @@
1
+ module Statinize
2
+ class Attribute
3
+ module Options
4
+ class Conditions < Array
5
+ def truthy?(instance)
6
+ all? do |condition|
7
+ if condition.is_a?(Symbol)
8
+ !!condition.to_proc.call(instance)
9
+ elsif condition.lambda?
10
+ !!instance.instance_exec(&condition)
11
+ elsif condition.is_a?(Proc)
12
+ !!condition.call(instance)
13
+ else
14
+ raise InvalidConditionError, "condition should be a Symbol, Proc, or a lambda"
15
+ end
16
+ end
17
+ end
18
+
19
+ def falsey?(instance)
20
+ all? do |condition|
21
+ if condition.is_a?(Symbol)
22
+ !condition.to_proc.call(instance)
23
+ elsif condition.lambda?
24
+ !instance.instance_exec(&condition)
25
+ elsif condition.is_a?(Proc)
26
+ !condition.call(instance)
27
+ else
28
+ raise InvalidConditionError, "condition should be a Symbol, Proc, or a lambda"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,65 @@
1
+ module Statinize
2
+ class Attribute
3
+ module Options
4
+ def self.extended(instance)
5
+ raise "Cannot extend #{instance}" unless instance.is_a? Hash
6
+
7
+ instance.transform_values! do |value|
8
+ if %i[if unless].include?(instance.key(value))
9
+ Conditions.new(Array(value))
10
+ else
11
+ value
12
+ end
13
+ end
14
+ end
15
+
16
+ def validators
17
+ @validators ||= defined_validators
18
+ .transform_keys { |validator| Object.const_get validator }
19
+ end
20
+
21
+ def all_validators_defined?
22
+ validators_class_names
23
+ .all? { |validator, _| Object.const_defined? validator }
24
+ end
25
+
26
+ def should_cast?
27
+ key?(:type) && key?(:cast)
28
+ end
29
+
30
+ def should_validate?(instance)
31
+ return false if validators.empty?
32
+ return true unless key?(:unless) || key?(:if)
33
+ return false if key?(:unless) && !unless_passed?(instance)
34
+
35
+ if_passed?(instance)
36
+ end
37
+
38
+ private
39
+
40
+ def unless_passed?(instance)
41
+ self[:unless].falsey?(instance)
42
+ end
43
+
44
+ def if_passed?(instance)
45
+ self[:if].truthy?(instance)
46
+ end
47
+
48
+ def filtered_validators
49
+ reject do |k, _|
50
+ ::Statinize::Validator::NOT_VALIDATORS.include? k
51
+ end
52
+ end
53
+
54
+ def defined_validators
55
+ validators_class_names
56
+ .select { |validator, _| Object.const_defined? validator }
57
+ end
58
+
59
+ def validators_class_names
60
+ filtered_validators
61
+ .transform_keys { |validator| "::Statinize::#{validator.to_s.capitalize}Validator" }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,11 @@
1
+ module Statinize
2
+ class Attribute
3
+ class OptionsCollection < Array
4
+ def all_validators_defined?
5
+ raise "Not an option!" unless all? { _1.respond_to? :validators }
6
+
7
+ all?(&:all_validators_defined?)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,25 +2,39 @@ module Statinize
2
2
  class Attribute
3
3
  include Comparable
4
4
 
5
- attr_reader :klass, :name, :options, :validators
5
+ attr_reader :klass, :name, :options
6
6
 
7
- def initialize(klass, name, options)
7
+ def initialize(klass, name, opts)
8
8
  @klass = klass
9
9
  @name = name
10
- @options = options
11
- @validators = options.keys
10
+ @options = OptionsCollection.new
11
+ @options << opts.clone.extend(Options) unless opts.empty?
12
12
  end
13
13
 
14
- def self.create(klass, name, options)
15
- new(klass, name, options).create
14
+ def self.create(klass, name, opts)
15
+ new(klass, name, opts).create
16
16
  end
17
17
 
18
18
  def create
19
- statinizer.add_attribute(self) unless attribute_exists?
20
- klass.send(:attr_accessor, name)
19
+ statinizer.add_attribute(self) unless attribute?
20
+ klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
21
+ def #{name}
22
+ @#{name}
23
+ end
24
+
25
+ def #{name}=(attr)
26
+ @#{name} = attr
27
+ validate if respond_to?(:validate)
28
+ attr
29
+ end
30
+ RUBY_EVAL
21
31
  self
22
32
  end
23
33
 
34
+ def add_options(opts)
35
+ options << opts.extend(Options)
36
+ end
37
+
24
38
  def <=>(other)
25
39
  name == other.name &&
26
40
  options == other.options &&
@@ -33,8 +47,8 @@ module Statinize
33
47
  klass.statinizer
34
48
  end
35
49
 
36
- def attribute_exists?
37
- statinizer.attribute_exists?(self)
50
+ def attribute?
51
+ statinizer.attribute?(self)
38
52
  end
39
53
  end
40
54
  end
@@ -8,14 +8,14 @@ module Statinize
8
8
  Rational => :to_r,
9
9
  Symbol => :to_sym,
10
10
  Enumerator => :to_enum,
11
- Proc => :to_proc
11
+ Proc => :to_proc,
12
12
  }.freeze
13
13
 
14
- def initialize(instance, attr_name, attr_value, validator_value)
14
+ def initialize(instance, attr, option)
15
15
  @instance = instance
16
- @attr_name = attr_name
17
- @attr_value = attr_value
18
- @validator_value = validator_value
16
+ @attr_name = attr.name
17
+ @attr_value = instance.public_send(attr_name)
18
+ @validator_value = option[:type]
19
19
  end
20
20
 
21
21
  def cast
@@ -25,10 +25,6 @@ module Statinize
25
25
  true
26
26
  end
27
27
 
28
- def self.cast(instance, attr_name, attr_value, validator_value)
29
- new(instance, attr_name, attr_value, validator_value).cast
30
- end
31
-
32
28
  private
33
29
 
34
30
  attr_reader :instance, :attr_name, :attr_value, :validator_value
@@ -0,0 +1,11 @@
1
+ module Statinize
2
+ class Configuration
3
+ include Singleton
4
+
5
+ attr_accessor :force
6
+
7
+ def self.configure
8
+ yield instance if block_given?
9
+ end
10
+ end
11
+ end
@@ -1,17 +1,17 @@
1
1
  module Statinize
2
- class NotStatinizableError < StandardError; end
3
-
4
2
  class ValidationError < StandardError; end
5
3
 
6
4
  class NoSuchValidatorError < StandardError; end
7
5
 
8
- class UncastableAttributeError < StandardError; end
6
+ class InvalidConditionError < StandardError; end
7
+
8
+ class UndefinedAttribute < StandardError; end
9
9
 
10
10
  class Errors < Array
11
11
  def to_s
12
12
  nice_errors = map do |i|
13
- "#{i.keys.first.to_s.capitalize} #{i.values.first}"
14
- end.join(', ')
13
+ "#{i.keys.first.to_s.split("_").tap { |attr| attr.first.capitalize! }.join(" ")} #{i.values.first}"
14
+ end.join("; ")
15
15
 
16
16
  "ValidationError: #{nice_errors}"
17
17
  end
@@ -6,48 +6,58 @@ module Statinize
6
6
  end
7
7
 
8
8
  module PrependedMethods
9
- def initialize(*args, **kwargs)
10
- self.class.statinizer.attrs.map(&:name).each do |attr|
11
- instance_variable_set("@#{attr}", kwargs.delete(attr)) if kwargs.key?(attr)
9
+ def initialize(*args, **kwargs, &block)
10
+ if private_methods(false).include? :initialize
11
+ super(*args, **kwargs, &block)
12
+ check_defined!(kwargs)
13
+ else
14
+ statinizer.attributes.map(&:name).each do |attr|
15
+ instance_variable_set("@#{attr}", kwargs[attr]) if kwargs.key?(attr)
16
+ end
12
17
  end
13
18
 
14
- validator.validate
19
+ define_validation
20
+ validate!
21
+ end
15
22
 
16
- super(*args, **kwargs)
23
+ def validation
24
+ @validation ||= Validation.new(statinizer, self)
17
25
  end
18
26
 
19
- def valid?
20
- validator.valid?
27
+ def attributes
28
+ @attributes = Hash[
29
+ statinizer.attributes.map { |a| [a.name, public_send(a.name)] }
30
+ ]
21
31
  end
22
32
 
23
- def errors
24
- validator.errors
33
+ alias_method :define_validation, :validation
34
+
35
+ def statinizer
36
+ self.class.statinizer
25
37
  end
26
38
 
27
39
  private
28
40
 
29
- def validator
30
- Validator.new(self)
41
+ def check_defined!(kwargs)
42
+ statinizer.attributes.map(&:name).each do |attr|
43
+ undefined_attrs << attr if public_send(attr) != kwargs[attr] || !kwargs.key?(attr)
44
+ end
45
+
46
+ raise UndefinedAttributeError, "Not all attributes defined in statinize block are defined in initialize"
31
47
  end
32
48
  end
33
49
 
34
50
  module ClassMethods
35
- def statinize(force: false, &block)
36
- instance_variable_set('@statinizer', Statinizer.new(self, force))
51
+ def statinize(&block)
52
+ @statinizer = Statinizer.new(self)
37
53
 
38
- class_eval do
39
- def self.statinizer
40
- @statinizer
41
- end
42
- end
54
+ statinizer.instance_eval(&block)
43
55
 
44
- block.call
56
+ statinizer.check_validators_exist!
45
57
  end
46
58
 
47
- def attributes(*attrs, **options)
48
- attrs.each do |attr|
49
- Attribute.create(self, attr, options)
50
- end
59
+ def statinizer
60
+ @statinizer
51
61
  end
52
62
  end
53
63
  end
@@ -1,27 +1,72 @@
1
1
  module Statinize
2
2
  class Statinizer
3
- attr_reader :attrs, :force
3
+ attr_reader :klass
4
4
 
5
- def initialize(klass, force)
5
+ def initialize(klass)
6
6
  @klass = klass
7
- @force = force
8
- @attrs = Set.new
7
+ @force = config.force
8
+ end
9
+
10
+ def attribute(*attrs, **options)
11
+ options.merge!(@with) if @with
12
+
13
+ attrs.each do |attr|
14
+ Attribute.create(klass, attr, options)
15
+ end
16
+ end
17
+
18
+ def validate(*attrs, **options)
19
+ options.merge!(@with) if @with
20
+
21
+ attrs.each do |attr|
22
+ attribute = attributes.find { _1.name == attr }
23
+ attribute&.add_options(options)
24
+ end
25
+ end
26
+
27
+ def with(**kwargs, &block)
28
+ @with = kwargs
29
+ instance_exec(&block)
30
+
31
+ remove_instance_variable(:@with) if @with
32
+ end
33
+
34
+ def force(force = nil)
35
+ force.nil? ? @force : @force = force
36
+ end
37
+
38
+ def self.configure(&block)
39
+ Configuration.configure(&block)
40
+ end
41
+
42
+ def config
43
+ @config ||= Configuration.instance
44
+ end
45
+
46
+ def force?
47
+ @force
9
48
  end
10
49
 
11
50
  def add_attribute(attribute)
12
- @attrs.add(attribute)
51
+ attributes.add(attribute)
13
52
  end
14
53
 
15
- def forced_attributes
16
- attrs.select { |attr| attr.validators.include? :force }
54
+ def attributes
55
+ @attributes ||= Set.new
17
56
  end
18
57
 
19
- def casted_attributes
20
- attrs.select { |attr| attr.validators.include? :cast }
58
+ def attribute?(attribute)
59
+ attributes.include? attribute
21
60
  end
22
61
 
23
- def attribute_exists?(attribute)
24
- attrs.include? attribute
62
+ def check_validators_exist!
63
+ raise NoSuchValidatorError unless all_validators_defined?
64
+ end
65
+
66
+ private
67
+
68
+ def all_validators_defined?
69
+ attributes.map { |attr| attr.options.all_validators_defined? }.all? { !!_1 }
25
70
  end
26
71
  end
27
72
  end
@@ -0,0 +1,90 @@
1
+ module Statinize
2
+ class Validation
3
+ INSTANCE_METHODS = %i[validate validate! valid? invalid? errors]
4
+
5
+ attr_reader :statinizer, :instance, :errors
6
+
7
+ def initialize(statinizer, instance)
8
+ @statinizer = statinizer
9
+ @instance = instance
10
+
11
+ define_instance_methods
12
+ end
13
+
14
+ def validate
15
+ @errors = Errors.new
16
+ @erroneous_attributes = Hash.new { |h, k| h[k] = Set.new }
17
+ @erroneous_forced_attributes = Hash.new { |h, k| h[k] = Set.new }
18
+
19
+ fill_errors
20
+ end
21
+
22
+ def validate!
23
+ validate
24
+
25
+ raise ValidationError, errors.to_s if should_raise?
26
+ end
27
+
28
+ def valid?
29
+ errors.empty?
30
+ end
31
+
32
+ def invalid?
33
+ !valid?
34
+ end
35
+
36
+ private
37
+
38
+ def fill_errors
39
+ attributes.each do |attr|
40
+ attr.options.each do |option|
41
+ next unless option.should_validate?(instance)
42
+
43
+ attr_value = instance.public_send(attr.name)
44
+
45
+ option.validators.each do |validator_class, validator_value|
46
+ validator_instance = validator_class.new(attr_value, validator_value)
47
+
48
+ next if validator_instance.valid?
49
+ next if validator_class == TypeValidator && cast(attr, option)
50
+
51
+ force = option[:force] ||
52
+ (statinizer.force? && option[:force].nil?)
53
+
54
+ erroneous_attributes[attr.name].add option
55
+ erroneous_forced_attributes[attr.name].add option if force
56
+ @errors << { attr.name => validator_instance.error }
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def cast(attr, option)
63
+ caster = Caster.new(instance, attr, option)
64
+
65
+ option.should_cast? && caster.cast
66
+ end
67
+
68
+ def should_raise?
69
+ return false if valid?
70
+
71
+ erroneous_attributes.keys.intersect?(erroneous_forced_attributes.keys)
72
+ end
73
+
74
+ def attributes
75
+ statinizer.attributes
76
+ end
77
+
78
+ def define_instance_methods
79
+ INSTANCE_METHODS.each do |meth|
80
+ instance.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
81
+ def #{meth}
82
+ validation.#{meth}
83
+ end
84
+ RUBY_EVAL
85
+ end
86
+ end
87
+
88
+ attr_reader :erroneous_attributes, :erroneous_forced_attributes
89
+ end
90
+ end
@@ -1,112 +1,20 @@
1
1
  module Statinize
2
2
  class Validator
3
- NOT_VALIDATORS = %i[force cast].freeze
3
+ NOT_VALIDATORS = %i[force cast if unless]
4
4
 
5
- def initialize(instance)
6
- @instance = instance
5
+ attr_accessor :attr_value, :validator_value
7
6
 
8
- raise NotStatinizableError unless instance&.class&.statinizer.is_a?(Statinizer)
9
- raise NoSuchValidatorError unless validators_exist?
10
-
11
- @errors = Errors.new
12
- @erroneous_attrs = Set.new
7
+ def initialize(attr_value, validator_value)
8
+ @attr_value = attr_value
9
+ @validator_value = validator_value
13
10
  end
14
11
 
15
12
  def valid?
16
- validate
17
- return false unless errors.empty?
18
-
19
- true
20
- end
21
-
22
- def invalid?
23
- !valid?
24
- end
25
-
26
- def validate
27
- attrs.each do |attr|
28
- attr.options.each do |validator, validator_value|
29
- next if NOT_VALIDATORS.include? validator
30
-
31
- attr_name = attr.name
32
-
33
- validator_class = Object.const_get("Statinize::#{validator.to_s.capitalize}Validator")
34
-
35
- validator_instance = validator_class.new(instance)
36
-
37
- validator_class.public_send(
38
- :attr_accessor,
39
- *%i[attr_name attr_value validator_value]
40
- )
41
-
42
- attr_value = instance.public_send attr_name
43
-
44
- validator_instance.instance_variable_set('@attr_name', attr_name)
45
- validator_instance.instance_variable_set('@attr_value', attr_value)
46
- validator_instance.instance_variable_set('@validator_value', validator_value)
47
-
48
- next if validator_instance.valid?
49
- next if should_cast?(validator, attr_name) && casted?(attr_name, attr_value, validator_value)
50
-
51
- @erroneous_attrs.add attr_name
52
- add_error validator_instance.error
53
- end
54
- end
55
-
56
- raise ValidationError, errors.to_s if should_raise?
57
- end
58
-
59
- protected
60
-
61
- attr_reader :instance, :errors
62
-
63
- def statinizer
64
- @statinizer ||= instance.class.statinizer
65
- end
66
-
67
- def attrs
68
- statinizer.attrs
69
- end
70
-
71
- def force?
72
- statinizer.force
73
- end
74
-
75
- def should_raise?
76
- force? || @erroneous_attrs.intersect?(forced_attributes.map(&:name))
77
- end
78
-
79
- def forced_attributes
80
- statinizer.forced_attributes
81
- end
82
-
83
- def casted_attributes
84
- statinizer.casted_attributes
85
- end
86
-
87
- def add_error(error)
88
- return if error.nil?
89
-
90
- @errors << error
91
- end
92
-
93
- private
94
-
95
- def validators_exist?
96
- attrs
97
- .flat_map(&:validators).uniq
98
- .reject { |validator| NOT_VALIDATORS.include? validator }
99
- .map(&:to_s).map(&:capitalize)
100
- .all? { |validator| Object.const_defined?("Statinize::#{validator}Validator") }
101
- end
102
-
103
- def should_cast?(validator, attr_name)
104
- validator == :type &&
105
- statinizer.casted_attributes.map(&:name).include?(attr_name)
13
+ raise NoMethodError, "#valid? method is not implemented on a #{self.class}"
106
14
  end
107
15
 
108
- def casted?(attr_name, attr_value, validator_value)
109
- ::Statinize::Caster.cast(instance, attr_name, attr_value, validator_value)
16
+ def error
17
+ raise NoMethodError, "#error method is not implemented on a #{self.class}"
110
18
  end
111
19
  end
112
20
  end
@@ -0,0 +1,11 @@
1
+ module Statinize
2
+ class InclusionValidator < Validator
3
+ def valid?
4
+ validator_value.is_a?(Array) && validator_value.include?(attr_value)
5
+ end
6
+
7
+ def error
8
+ "should be one of #{validator_value.join(", ")}"
9
+ end
10
+ end
11
+ end
@@ -9,7 +9,7 @@ module Statinize
9
9
  end
10
10
 
11
11
  def error
12
- { attr_name => 'is blank' }
12
+ "is blank"
13
13
  end
14
14
 
15
15
  private
@@ -23,7 +23,7 @@ module Statinize
23
23
  end
24
24
 
25
25
  def empty_string?
26
- attr_value == ''
26
+ attr_value == ""
27
27
  end
28
28
  end
29
29
  end
@@ -1,11 +1,11 @@
1
1
  module Statinize
2
2
  class TypeValidator < Validator
3
3
  def valid?
4
- attr_value.is_a?(validator_value) || attr_value.nil?
4
+ attr_value.is_a?(validator_value)
5
5
  end
6
6
 
7
7
  def error
8
- { attr_name => "should be #{validator_value}, found #{attr_value.class} instead" }
8
+ "should be #{validator_value}, found #{attr_value.class} instead"
9
9
  end
10
10
  end
11
11
  end
data/lib/statinize.rb CHANGED
@@ -1,10 +1,17 @@
1
- require 'set'
2
- require 'pry'
3
- require_relative 'statinize/validator'
4
- require_relative 'statinize/caster'
5
- require_relative 'statinize/errors'
6
- require_relative 'statinize/validators/type_validator'
7
- require_relative 'statinize/validators/presence_validator'
8
- require_relative 'statinize/attribute'
9
- require_relative 'statinize/statinizer'
10
- require_relative 'statinize/statinizable'
1
+ require "set"
2
+ require "pry"
3
+ require "singleton"
4
+ require_relative "statinize/statinizable"
5
+ require_relative "statinize/statinizer"
6
+ require_relative "statinize/configuration"
7
+ require_relative "statinize/attribute"
8
+ require_relative "statinize/attribute/options"
9
+ require_relative "statinize/attribute/options/conditions"
10
+ require_relative "statinize/attribute/options_collection"
11
+ require_relative "statinize/validator"
12
+ require_relative "statinize/validation"
13
+ require_relative "statinize/validators/type_validator"
14
+ require_relative "statinize/validators/presence_validator"
15
+ require_relative "statinize/validators/inclusion_validator"
16
+ require_relative "statinize/caster"
17
+ require_relative "statinize/errors"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statinize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Barseek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-01 00:00:00.000000000 Z
11
+ date: 2022-07-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Statinization gem. Allows for creation of attributes for a class with
14
14
  a given type.
@@ -19,11 +19,17 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - lib/statinize.rb
21
21
  - lib/statinize/attribute.rb
22
+ - lib/statinize/attribute/options.rb
23
+ - lib/statinize/attribute/options/conditions.rb
24
+ - lib/statinize/attribute/options_collection.rb
22
25
  - lib/statinize/caster.rb
26
+ - lib/statinize/configuration.rb
23
27
  - lib/statinize/errors.rb
24
28
  - lib/statinize/statinizable.rb
25
29
  - lib/statinize/statinizer.rb
30
+ - lib/statinize/validation.rb
26
31
  - lib/statinize/validator.rb
32
+ - lib/statinize/validators/inclusion_validator.rb
27
33
  - lib/statinize/validators/presence_validator.rb
28
34
  - lib/statinize/validators/type_validator.rb
29
35
  homepage: