smart_core 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fafaa0194f7b04c748fbfcd665bfbd0d13b7ce822cddc66694e0b8c417bd2c63
4
- data.tar.gz: ba54750965b25c714c87696ad0c5dbe1cb1d1ebfaf2b93631afcbd79687bca18
3
+ metadata.gz: 386622ee9696b78aaf7e86cfcc5eaa6e11591bab6607a355d85b9fbfa90d513a
4
+ data.tar.gz: 9a905a991a12f36dc775076afa4f9069c099c33351e1d19911be4087789f8858
5
5
  SHA512:
6
- metadata.gz: c1a4572366fc8cc0047cd30b9513002c32936f3bfc565887cd245fb51ca68b01e89aa85fae9bd26bb725fd9b51b787db798e0955a399b5ebd1b6fea016d36c5f
7
- data.tar.gz: d5d32d60ea55b12590a39b178711cf8c2f354e0565e9a6e863544776764e7951213e5886fca9fc21153e65020c5ca7f94a91ed50881990f477c436050417b295
6
+ metadata.gz: 1d7d6a650e4b4656e932883da4669b3eb2659a1b32e68d70db5eaf48f6d37edd8470ac2400ab3822df60a475ebd669c761899ed08d4613282ef8738be22eda5f
7
+ data.tar.gz: 648eb736394268e455e478431df012b7658cb434e8fc5ab506fe16f3f81a005fd7fa3fa1c1d1777a0395cd8899490ffc1a47c1f3e05314cf5a1aca078c4662b1
data/.gitignore CHANGED
@@ -1,11 +1,13 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
+ /Gemfile.lock
3
4
  /_yardoc/
4
5
  /coverage/
5
6
  /doc/
6
7
  /pkg/
7
8
  /spec/reports/
8
9
  /tmp/
9
-
10
- # rspec failure tracking
11
10
  .rspec_status
11
+ .ruby-version
12
+ gemfiles/*.lock
13
+ *.gem
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
- --format documentation
2
1
  --color
2
+ --format=progress
3
3
  --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,15 @@
1
+ inherit_gem:
2
+ armitage-rubocop:
3
+ - lib/rubocop.general.yml
4
+ - lib/rubocop.rspec.yml
5
+
6
+ AllCops:
7
+ UseCache: true
8
+ TargetRubyVersion: 2.6.0
9
+ Include:
10
+ - bin/console
11
+ - lib/**/*.rb
12
+ - spec/**/*.rb
13
+ - Gemfile
14
+ - Rakefile
15
+ - smart_core.gemspec
data/.travis.yml CHANGED
@@ -1,5 +1,16 @@
1
- sudo: false
2
1
  language: ruby
2
+ sudo: false
3
+ before_install: gem install bundler
4
+ cache: bundler
3
5
  rvm:
4
- - 2.5.1
5
- before_install: gem install bundler -v 1.16.2
6
+ - 2.3.8
7
+ - 2.4.5
8
+ - 2.5.3
9
+ - 2.6.0
10
+ - ruby-head
11
+ - jruby-head
12
+ script:
13
+ - bundle exec rspec
14
+ notifications:
15
+ slack:
16
+ secure: CUM3xnSJvn/y6Z+epQrO5uSk1EUuP4S+ugcS+48nlWD6eEzhYZwcBAK9tvD6kYGOPzoWyKSbW6Is1kzSunNr6f8ICc8HtrVHzoHE8UCzwZ7FpjgD0JgMgC4+xLLBO6lV0Hc7dNh4KLAhy4Zvbr09DPjcG8UW9/QgUdn+wBf9LHMgA+2SpBPEzMwz6pMbWW/+kkoCz2KU877kEfuFFGfvoOEFWTxUUhuCYi5a1SmTjm9A7CFTEWNkX8yHJwdgVgONjvm9lnhTeV0DvMeEOhk8Kx/hzQ2OGIicXZXWyZFkzNzpQm5xE12Hs4rY7mGPbo3bsYRDH9Ys6yf4fpNf0oZ/1agzl/gnDzacA2wL2b+eq1uZblE/fdwrNp+GIH0R+Ec2rsVKM7kh71sA6Xp1pzFb2DnHj5O4QFaPMsLyIZqD0BkVt4pNfwi4ZUB56sWkYeRQGm8x2Beh02IyFfxI65lY5kZrW6GjXx5eFeOHcmzQzeADWLSBf4iw01G/A/QqwZzlZ3fm0fRJi8BKDh3Jj0IrGndE9Bl+E7uOClKKngTPxOVLOLo/vY5mfI8M94f99kr0J45XkoAdc0SQfi3hDa/tHaakI5GJsmByjgOQwV7OU/2wzJoXAg/RPYjsYcwnTgzm9bLXPhQzfM8rUS5vLTOWIW+N0QKmWml7eWVtiedcwXA=
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ ## [0.1.0] - 2019-01-05
5
+ ### Added
6
+ - Validation object abstraction;
data/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
6
 
5
7
  # Specify your gem's dependencies in smart_core.gemspec
6
8
  gemspec
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018 Rustam Ibragimov
3
+ Copyright (c) 2018-2019 Rustam Ibragimov
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,43 +1,12 @@
1
- # SmartCore
1
+ # SmartCore · [![Build Status](https://travis-ci.org/0exp/smart_core.svg?branch=master)](https://travis-ci.org/0exp/smart_core) [![Coverage Status](https://coveralls.io/repos/github/0exp/smart_core/badge.svg?branch=master)](https://coveralls.io/github/0exp/smart_core?branch=master)
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/smart_core`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ In active development.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ #### Completed abstractions:
6
6
 
7
- ## Installation
8
-
9
- Add this line to your application's Gemfile:
10
-
11
- ```ruby
12
- gem 'smart_core'
13
- ```
14
-
15
- And then execute:
16
-
17
- $ bundle
18
-
19
- Or install it yourself as:
20
-
21
- $ gem install smart_core
22
-
23
- ## Usage
24
-
25
- TODO: Write usage instructions here
26
-
27
- ## Development
28
-
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
-
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
-
33
- ## Contributing
34
-
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/smart_core. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
-
37
- ## License
38
-
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
-
41
- ## Code of Conduct
42
-
43
- Everyone interacting in the SmartCore project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/smart_core/blob/master/CODE_OF_CONDUCT.md).
7
+ - **Validation object** (`SmartCore::Validator`)
8
+ - support for nested validations;
9
+ - inheritance works as expected `:)`;
10
+ - command-style DSL;
11
+ - thread-safe;
12
+ - no dependencies;
data/Rakefile CHANGED
@@ -1,6 +1,18 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
3
2
 
4
- RSpec::Core::RakeTask.new(:spec)
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop'
6
+ require 'rubocop-rspec'
7
+ require 'rubocop/rake_task'
5
8
 
6
- task :default => :spec
9
+ RuboCop::RakeTask.new(:rubocop) do |t|
10
+ config_path = File.expand_path(File.join('.rubocop.yml'), __dir__)
11
+
12
+ t.options = ['--config', config_path]
13
+ t.requires << 'rubocop-rspec'
14
+ end
15
+
16
+ RSpec::Core::RakeTask.new(:rspec)
17
+
18
+ task default: :rspec
data/bin/console CHANGED
@@ -1,14 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "smart_core"
4
+ require 'bundler/setup'
5
+ require 'smart_core'
5
6
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
7
+ require 'pry'
8
+ Pry.start
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class SmartCore::Validator::Attribute
6
+ # @return [Symbol]
7
+ #
8
+ # @api private
9
+ # @since 0.1.0
10
+ attr_reader :name
11
+
12
+ # @param name [String, Symbol]
13
+ # @param default_value [Proc, Object]
14
+ # @return [void]
15
+ #
16
+ # @api private
17
+ # @since 0.1.0
18
+ def initialize(name, default_value = nil)
19
+ unless name.is_a?(Symbol) || name.is_a?(String)
20
+ raise(
21
+ SmartCore::Validator::IncorrectAttributeNameError,
22
+ 'Attribute name should be a symbol or a string'
23
+ )
24
+ end
25
+
26
+ @name = name.to_sym
27
+ @default_value = default_value
28
+ end
29
+
30
+ # @return [Any]
31
+ #
32
+ # @api private
33
+ # @since 0.1.0
34
+ def default_value
35
+ @default_value.is_a?(Proc) ? @default_value.call : @default_value
36
+ end
37
+
38
+ # @return [SmartCore::Validator::Attribute]
39
+ #
40
+ # @api private
41
+ # @since 0.1.0
42
+ def dup
43
+ self.class.new(name, @default_value)
44
+ end
45
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class SmartCore::Validator::AttributeSet
6
+ # @since 0.1.0
7
+ include Enumerable
8
+
9
+ # @return [Hash<Symbol, SmartCore::Validator::Attribute>]
10
+ #
11
+ # @api private
12
+ # @since 0.1.0
13
+ attr_reader :attributes
14
+
15
+ # @return [void]
16
+ #
17
+ # @api private
18
+ # @since 0.1.0
19
+ def initialize
20
+ @attributes = {}
21
+ @access_lock = Mutex.new
22
+ end
23
+
24
+ # @param attribute [Symbiont::Validator::Attribute]
25
+ # @return [void]
26
+ #
27
+ # @api private
28
+ # @since 0.1.0
29
+ def add_attribute(attribute)
30
+ thread_safe { attributes[attribute.name] = attribute }
31
+ end
32
+ alias_method :<<, :add_attribute
33
+
34
+ # @param attribute_set [SmartCore::Validator::AttributeSet]
35
+ # @return [void]
36
+ #
37
+ # @api private
38
+ # @sinec 0.1.0
39
+ def concat(attribute_set)
40
+ thread_safe { attributes.merge!(attribute_set.dup.attributes) }
41
+ end
42
+
43
+ # @return [SmartCore::Validator::AttributeSet]
44
+ #
45
+ # @api private
46
+ # @since 0.1.0
47
+ def dup
48
+ thread_safe do
49
+ self.class.new.tap do |duplicate|
50
+ attributes.each_value do |attribute|
51
+ duplicate.add_attribute(attribute.dup)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # @return [Enumerable]
58
+ #
59
+ # @api private
60
+ # @since 0.1.0
61
+ def each(&block)
62
+ thread_safe { block_given? ? attributes.each_value(&block) : attributes.each_value }
63
+ end
64
+
65
+ private
66
+
67
+ # @param block [Proc]
68
+ # @return [Any]
69
+ #
70
+ # @api private
71
+ # @since 0.1.0
72
+ def thread_safe(&block)
73
+ @access_lock.synchronize(&block)
74
+ end
75
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class SmartCore::Validator::CommandSet
6
+ # @since 0.1.0
7
+ include Enumerable
8
+
9
+ # @return [Array<SmartCore::Validator::Commands::Base>]
10
+ #
11
+ # @api private
12
+ # @since 0.1.0
13
+ attr_reader :commands
14
+
15
+ # @api private
16
+ # @since 0.1.0
17
+ def initialize
18
+ @commands = []
19
+ @access_lock = Mutex.new
20
+ end
21
+
22
+ # @param command [SmartCore::Validator::Commands::Base]
23
+ # @return [void]
24
+ #
25
+ # @api private
26
+ # @since 0.1.0
27
+ def add_command(command)
28
+ thread_safe { commands << command }
29
+ end
30
+ alias_method :<<, :add_command
31
+
32
+ # @yield [SmartCore::Validator::Commands::Base]
33
+ # @return [Enumerable]
34
+ #
35
+ # @api private
36
+ # @since 0.1.0
37
+ def each(&block)
38
+ thread_safe { block_given? ? commands.each(&block) : commands.each }
39
+ end
40
+
41
+ # @param command_set [SmartCore::Validator::CommandSet]
42
+ # @return [void]
43
+ #
44
+ # @api private
45
+ # @since 0.1.0
46
+ def concat(command_set)
47
+ thread_safe { commands.concat(command_set.commands) }
48
+ end
49
+
50
+ # @return [void]
51
+ #
52
+ # @api private
53
+ # @since 0.1.0
54
+ def clear
55
+ thread_safe { commands.clear }
56
+ end
57
+
58
+ private
59
+
60
+ # @param block [Proc]
61
+ # @return [Any]
62
+ #
63
+ # @api private
64
+ # @since 0.1.0
65
+ def thread_safe(&block)
66
+ @access_lock.synchronize(&block)
67
+ end
68
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator::Commands
4
+ # @api private
5
+ # @since 0.1.0
6
+ class AddNestedValidations < Base
7
+ # @since 0.1.0
8
+ include WorkWithNestedsMixin
9
+
10
+ # @return [Symbol, String]
11
+ #
12
+ # @api private
13
+ # @since 0.1.0
14
+ attr_reader :validating_method
15
+
16
+ # @return [Proc]
17
+ #
18
+ # @api private
19
+ # @since 0.1.0
20
+ attr_reader :nested_validations
21
+
22
+ # @param validating_method [Symbol, String]
23
+ # @param nested_validations [Proc]
24
+ # @return [void]
25
+ #
26
+ # @api private
27
+ # @since 0.1.0
28
+ def initialize(validating_method, nested_validations)
29
+ @validating_method = validating_method
30
+ @nested_validations = nested_validations
31
+ end
32
+
33
+ # @param validator [SmartCore::Validator]
34
+ # @return [void]
35
+ #
36
+ # @api private
37
+ # @since 0.1.0
38
+ def call(validator)
39
+ errors = SmartCore::Validator::Invoker.call(validator, validating_method)
40
+
41
+ if errors.empty?
42
+ check_nested_validations(validator, nested_validations)
43
+ else
44
+ validator.__append_errors__(errors)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator::Commands
4
+ # @api private
5
+ # @since 0.1.0
6
+ class AddValidation < Base
7
+ # @return [Symbol, String]
8
+ #
9
+ # @api private
10
+ # @since 0.1.0
11
+ attr_reader :validating_method
12
+
13
+ # @param validating_method [String, Symbol]
14
+ # @return [void]
15
+ #
16
+ # @api private
17
+ # @since 0.1.0
18
+ def initialize(validating_method)
19
+ @validating_method = validating_method
20
+ end
21
+
22
+ # @param validator [SmartCore::Validator]
23
+ # @return [void]
24
+ #
25
+ # @api private
26
+ # @since 0.1.0
27
+ def call(validator)
28
+ errors = SmartCore::Validator::Invoker.call(validator, validating_method)
29
+ validator.__append_errors__(errors) unless errors.empty?
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class SmartCore::Validator::Commands::Base
6
+ # @param validator [SmartCore::Validator]
7
+ # @return [void]
8
+ #
9
+ # @api private
10
+ # @since 0.1.0
11
+ def call(validator); end
12
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator::Commands
4
+ # @api private
5
+ # @since 0.1.0
6
+ class ValidateWith < Base
7
+ # @since 0.1.0
8
+ include WorkWithNestedsMixin
9
+
10
+ # @return [Class<SmartCore::Validator>]
11
+ #
12
+ # @api private
13
+ # @since 0.1.0
14
+ attr_reader :validating_klass
15
+
16
+ # @return [Proc]
17
+ #
18
+ # @api private
19
+ # @since 0.1.0
20
+ attr_reader :nested_validations
21
+
22
+ # @param validating_klass [Class<SmartCore::Validator>]
23
+ # @param nested_validations [Proc]
24
+ # @return [void]
25
+ #
26
+ # @api private
27
+ # @since 0.1.0
28
+ def initialize(validating_klass, nested_validations)
29
+ @validating_klass = validating_klass
30
+ @nested_validations = nested_validations
31
+ end
32
+
33
+ # @param validator [SmartCore::Validator]
34
+ # @return [void]
35
+ #
36
+ # @api private
37
+ # @since 0.1.0
38
+ def call(validator)
39
+ sub_validator = build_sub_validator(validator, validating_klass)
40
+
41
+ if sub_validator.valid?
42
+ check_nested_validations(validator, nested_validations)
43
+ else
44
+ validator.__append_errors__(sub_validator.__validation_errors__)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ module SmartCore::Validator::Commands::WorkWithNestedsMixin
6
+ # @param validator [SmartCore::Validator]
7
+ # @param nested_validations [Proc]
8
+ # @return [void]
9
+ #
10
+ # @api private
11
+ # @since 0.1.0
12
+ def check_nested_validations(validator, nested_validations)
13
+ nested_validator = build_nested_validator(validator, nested_validations)
14
+
15
+ unless nested_validator.valid?
16
+ validator.__append_errors__(nested_validator.__validation_errors__)
17
+ end
18
+ end
19
+
20
+ # @param validator [SmartCore::Validator]
21
+ # @param nested_validations [Proc]
22
+ # @return [SmartCore::Validator]
23
+ #
24
+ # @api private
25
+ # @since 0.1.0
26
+ def build_nested_validator(validator, nested_validations)
27
+ Class.new(validator.class).tap do |klass|
28
+ klass.clear_commands
29
+ klass.instance_eval(&nested_validations)
30
+ end.new(**validator.__attributes__)
31
+ end
32
+
33
+ # @param validator [SmartCore::Validator]
34
+ # @param another_validating_klass [Class<SmartCore::Validator>]
35
+ # @return [SmartCore::Validator]
36
+ #
37
+ # @api private
38
+ # @since 0.1.0
39
+ def build_sub_validator(validator, another_validating_klass)
40
+ another_validating_klass.new(**validator.__attributes__)
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator::Commands
4
+ require_relative 'commands/base'
5
+ require_relative 'commands/work_with_nesteds_mixin'
6
+ require_relative 'commands/add_validation'
7
+ require_relative 'commands/add_nested_validations'
8
+ require_relative 'commands/validate_with'
9
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class SmartCore::Validator
6
+ module DSL
7
+ class << self
8
+ # @param base_klass [Class]
9
+ # @return [void]
10
+ #
11
+ # @api private
12
+ # @since 0.1.0
13
+ def extended(base_klass)
14
+ base_klass.instance_variable_set(:@__commands__, CommandSet.new)
15
+ base_klass.instance_variable_set(:@__attributes__, AttributeSet.new)
16
+
17
+ base_klass.singleton_class.prepend(Module.new do
18
+ def inherited(child_klass)
19
+ child_klass.instance_variable_set(:@__commands__, CommandSet.new)
20
+ child_klass.instance_variable_set(:@__attributes__, AttributeSet.new)
21
+
22
+ child_klass.commands.concat(commands)
23
+ child_klass.attributes.concat(attributes)
24
+
25
+ super(child_klass)
26
+ end
27
+ end)
28
+ end
29
+ end
30
+
31
+ # @param attribute_name [String, Symbol]
32
+ # @return [void]
33
+ #
34
+ # @api private
35
+ # @since 0.1.0
36
+ def attribute(attribute_name, default: nil)
37
+ attribute = SmartCore::Validator::Attribute.new(attribute_name, default)
38
+ attributes << attribute
39
+ attr_reader attribute.name
40
+ end
41
+
42
+ # @return [SmartCore::Validator::AttributeSet]
43
+ #
44
+ # @api private
45
+ # @since 0.1.0
46
+ def attributes
47
+ @__attributes__
48
+ end
49
+
50
+ # @return [SmartCore::Validator::CommandSet]
51
+ #
52
+ # @api private
53
+ # @since 0.1.0
54
+ def commands
55
+ @__commands__
56
+ end
57
+
58
+ # @return [void]
59
+ #
60
+ # @api private
61
+ # @since 0.1.0
62
+ def clear_commands
63
+ commands.clear
64
+ end
65
+
66
+ # @param validating_method [Symbol, String]
67
+ # @param nested_validations [Proc]
68
+ # @return [void]
69
+ #
70
+ # @see SmartCore::Validator::Commands::AddValidation
71
+ # @see SmartCore::Validator::Commands::AddNestedValidations
72
+ #
73
+ # @api public
74
+ # @since 0.1.0
75
+ def validate(validating_method, &nested_validations)
76
+ if block_given?
77
+ commands << Commands::AddNestedValidations.new(validating_method, nested_validations)
78
+ else
79
+ commands << Commands::AddValidation.new(validating_method)
80
+ end
81
+ end
82
+
83
+ # @param validating_klass [Class<SmartCore::Validator>]
84
+ # @param nested_validations [Proc]
85
+ # @return [void]
86
+ #
87
+ # @see SmartCore::Validator::Commands::ValidateWith
88
+ #
89
+ # @api private
90
+ # @since 0.1.0
91
+ def validate_with(validating_klass, &nested_validations)
92
+ commands << Commands::ValidateWith.new(validating_klass, nested_validations)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Validator
4
+ # @api private
5
+ # @since 0.1.0
6
+ class ErrorSet
7
+ # @return [void]
8
+ #
9
+ # @api private
10
+ # @since 0.1.0
11
+ def initialize
12
+ @errors = Set.new
13
+ @access_lock = Mutex.new
14
+ end
15
+
16
+ # @param error_codes [Arrray<Symbol>]
17
+ # @return [void]
18
+ #
19
+ # @api private
20
+ # @since 0.1.0
21
+ def add_error(error_code)
22
+ thread_safe { store_error(error_code) }
23
+ end
24
+
25
+ # @param error_set [SmartCore::Validator::ErrorSet]
26
+ # @return [void]
27
+ #
28
+ # @api private
29
+ # @since 0.1.0
30
+ def concat(error_set)
31
+ thread_safe do
32
+ error_set.codes.each do |error_code|
33
+ store_error(error_code)
34
+ end
35
+ end
36
+ end
37
+
38
+ # @return [Boolean]
39
+ #
40
+ # @api private
41
+ # @since 0.1.0
42
+ def empty?
43
+ thread_safe { errors.empty? }
44
+ end
45
+
46
+ # @return [void]
47
+ #
48
+ # @api private
49
+ # @since 0.1.0
50
+ def clear
51
+ thread_safe { errors.clear }
52
+ end
53
+
54
+ # @return [Array<Symbol>]
55
+ #
56
+ # @api private
57
+ # @since 0.1.0
58
+ def codes
59
+ thread_safe { errors.to_a }
60
+ end
61
+
62
+ private
63
+
64
+ # @return [Array<Symbol>]
65
+ #
66
+ # @api private
67
+ # @since 0.1.0
68
+ attr_reader :errors
69
+
70
+ # @param error_code [Symbol, String]
71
+ # @return [void]
72
+ #
73
+ # @raise [SmartCore::Validator::IncorrectErrorCodeError]
74
+ #
75
+ # @api private
76
+ # @since 0.1.0
77
+ def store_error(error_code)
78
+ unless error_code.is_a?(Symbol) || error_code.is_a?(String)
79
+ raise IncorrectErrorCodeError, 'Error code should be a symbol or a string'
80
+ end
81
+
82
+ errors << error_code.to_sym
83
+ end
84
+
85
+ # @param block [Proc]
86
+ # @return [Any]
87
+ #
88
+ # @api private
89
+ # @since 0.1.0
90
+ def thread_safe(&block)
91
+ @access_lock.synchronize(&block)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Validator
4
+ # @api public
5
+ # @since 0.1.0
6
+ Error = Class.new(StandardError)
7
+
8
+ # @api public
9
+ # @since 0.1.0
10
+ IncorrectErrorCodeError = Class.new(Error)
11
+
12
+ # @api public
13
+ # @since 0.1.0
14
+ IncorrectAttributeNameError = Class.new(Error)
15
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Validator
4
+ # @api private
5
+ # @since 0.1.0
6
+ class Invoker
7
+ class << self
8
+ # @param validator [SmartCore::Validator]
9
+ # @param validating_method [Symbol, String]
10
+ # @return [SmartCore::Validator::ErrorSet]
11
+ #
12
+ # @api private
13
+ # @since 0.1.0
14
+ def call(validator, validating_method)
15
+ new(validator).call(validating_method)
16
+ end
17
+ end
18
+
19
+ # @return [SmartCore::Validator]
20
+ #
21
+ # @api private
22
+ # @since 0.1.0k
23
+ attr_reader :validator
24
+
25
+ # @param validator [SmartCore::Validator]
26
+ # @return [void]
27
+ #
28
+ # @api private
29
+ # @since 0.1.0
30
+ def initialize(validator)
31
+ @validator = validator
32
+ @access_lock = Mutex.new
33
+ end
34
+
35
+ # @param validating_method [String, Symbol]
36
+ # @return [SmartCore::Validator::ErrorSet]
37
+ #
38
+ # @api private
39
+ # @since 0.1.0
40
+ def call(validating_method)
41
+ thread_safe do
42
+ ErrorSet.new.tap do |outer_errors|
43
+ extended_validator(outer_errors).send(validating_method)
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Creates new validator object cloned from the original validator object
51
+ # with the new functionality: error code interception.
52
+ #
53
+ # @param outer_errors [SmartCore::Validator::ErrorSet]
54
+ # @return [SmartCore::Validator]
55
+ #
56
+ # @api private
57
+ # @since 0.1.0
58
+ def extended_validator(outer_errors)
59
+ validator.dup.tap do |validator_clone|
60
+ validator_clone.define_singleton_method(:error) do |error_code|
61
+ outer_errors.add_error(error_code)
62
+ end
63
+ end
64
+ end
65
+
66
+ # @param block [Proc]
67
+ # @return [Any]
68
+ #
69
+ # @api private
70
+ # @since 0.1.0
71
+ def thread_safe(&block)
72
+ @access_lock.synchronize(&block)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.1.0
5
+ class SmartCore::Validator
6
+ require_relative 'validator/exceptions'
7
+ require_relative 'validator/command_set'
8
+ require_relative 'validator/attribute'
9
+ require_relative 'validator/attribute_set'
10
+ require_relative 'validator/error_set'
11
+ require_relative 'validator/invoker'
12
+ require_relative 'validator/commands'
13
+ require_relative 'validator/dsl'
14
+
15
+ # @since 0.1.0
16
+ extend DSL
17
+
18
+ class << self
19
+ # @param argumants [Any]
20
+ # @param options [Hash<Symbol, Object>]
21
+ # @return [void]
22
+ #
23
+ # @api public
24
+ # @since 0.1.0
25
+ def new(*arguments, **options)
26
+ allocate.tap do |object|
27
+ object.instance_variable_set(:@__validation_errors__, ErrorSet.new)
28
+ object.instance_variable_set(:@__invokation_lock__, Mutex.new)
29
+ object.instance_variable_set(:@__access_lock__, Mutex.new)
30
+
31
+ attributes.each do |attribute|
32
+ attribute_name = attribute.name
33
+
34
+ attribute_value =
35
+ if options.key?(attribute_name)
36
+ options[attribute_name]
37
+ else
38
+ attribute.default_value
39
+ end
40
+
41
+ object.instance_variable_set("@#{attribute_name}", attribute_value)
42
+ end
43
+
44
+ object.send(:initialize, *arguments, **options)
45
+ end
46
+ end
47
+ end
48
+
49
+ # @return [SmartCore::Validator::ErrorSet]
50
+ #
51
+ # @api private
52
+ # @since 0.1.0
53
+ attr_reader :__validation_errors__
54
+
55
+ # @return [void]
56
+ #
57
+ # @api public
58
+ # @since 0.1.0
59
+ def initialize(*, **); end
60
+
61
+ # @return [Boolean]
62
+ #
63
+ # @api public
64
+ # @since 0.1.0
65
+ def valid?
66
+ __thread_safe_invokation__ do
67
+ __validation_errors__.clear
68
+ self.class.commands.each { |command| command.call(self) }
69
+ __validation_errors__.empty?
70
+ end
71
+ end
72
+
73
+ # @return [Array<Symbol>]
74
+ #
75
+ # @api public
76
+ # @since 0.1.0
77
+ def errors
78
+ __thread_safe_access__ do
79
+ __validation_errors__.codes
80
+ end
81
+ end
82
+
83
+ # @param error_set [SmartCore::Validator::ErrorSet]
84
+ # @return [void]
85
+ #
86
+ # @api private
87
+ # @since 0.1.0
88
+ def __append_errors__(error_set)
89
+ __thread_safe_access__ do
90
+ __validation_errors__.concat(error_set)
91
+ end
92
+ end
93
+
94
+ # @return [Hash<Symbol, Object>]
95
+ #
96
+ # @api private
97
+ # @since 0.1.0
98
+ def __attributes__
99
+ __thread_safe_access__ do
100
+ self.class.attributes.each_with_object({}) do |attribute, accumulator|
101
+ accumulator[attribute.name] = instance_variable_get("@#{attribute.name}")
102
+ end
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # @param block [Proc]
109
+ # @return [Any]
110
+ #
111
+ # @api private
112
+ # @since 0.1.0
113
+ def __thread_safe_invokation__(&block)
114
+ @__invokation_lock__.synchronize(&block)
115
+ end
116
+
117
+ # @param block [Proc]
118
+ # @return [Any]
119
+ #
120
+ # @api private
121
+ # @since 0.1.0
122
+ def __thread_safe_access__(&block)
123
+ @__access_lock__.synchronize(&block)
124
+ end
125
+ end
@@ -1,3 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SmartCore
2
- VERSION = "0.0.0"
4
+ # @return [String]
5
+ #
6
+ # @api public
7
+ # @since 0.1.0
8
+ VERSION = '0.1.0'
3
9
  end
data/lib/smart_core.rb CHANGED
@@ -1,5 +1,8 @@
1
- require "smart_core/version"
1
+ # frozen_string_literal: true
2
2
 
3
+ # @api public
4
+ # @since 0.1.0
3
5
  module SmartCore
4
- # Your code goes here...
6
+ require_relative 'smart_core/version'
7
+ require_relative 'smart_core/validator'
5
8
  end
data/smart_core.gemspec CHANGED
@@ -1,29 +1,34 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "smart_core/version"
5
+ require 'smart_core/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "smart_core"
8
+ spec.required_ruby_version = '>= 2.3.8'
9
+
10
+ spec.name = 'smart_core'
8
11
  spec.version = SmartCore::VERSION
9
- spec.authors = ["Rustam Ibragimov"]
10
- spec.email = ["iamdaiver@icloud.com"]
12
+ spec.authors = ['Rustam Ibragimov']
13
+ spec.email = ['iamdaiver@icloud.com']
14
+ spec.summary = '(in active development) A set of common abstractions'
15
+ spec.description = '(in active development) A set of common abstractions'
16
+ spec.homepage = 'https://github.com/0exp/smart_core'
17
+ spec.license = 'MIT'
11
18
 
12
- spec.summary = 'Soon'
13
- spec.description = 'Soon'
14
- spec.homepage = "https://github.com/0exp/smart_core"
15
- spec.license = "MIT"
19
+ spec.bindir = 'bin'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
16
22
 
17
- # Specify which files should be added to the gem when it is released.
18
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|features)/}) }
21
25
  end
22
- spec.bindir = "exe"
23
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
- spec.require_paths = ["lib"]
25
26
 
26
- spec.add_development_dependency "bundler", "~> 1.16"
27
- spec.add_development_dependency "rake", "~> 10.0"
28
- spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_development_dependency 'coveralls', '~> 0.8.22'
28
+ spec.add_development_dependency 'simplecov', '~> 0.16.1'
29
+ spec.add_development_dependency 'armitage-rubocop', '~> 0.16.0'
30
+ spec.add_development_dependency 'rspec', '~> 3.8.0'
31
+ spec.add_development_dependency 'bundler', '~> 1.17'
32
+ spec.add_development_dependency 'rake', '~> 12.3'
33
+ spec.add_development_dependency 'pry', '~> 0.12'
29
34
  end
metadata CHANGED
@@ -1,58 +1,114 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-26 00:00:00.000000000 Z
11
+ date: 2019-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: coveralls
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.22
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.8.22
27
+ - !ruby/object:Gem::Dependency
28
+ name: simplecov
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.16.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.16.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: armitage-rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.16.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.16.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.8.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.8.0
13
69
  - !ruby/object:Gem::Dependency
14
70
  name: bundler
15
71
  requirement: !ruby/object:Gem::Requirement
16
72
  requirements:
17
73
  - - "~>"
18
74
  - !ruby/object:Gem::Version
19
- version: '1.16'
75
+ version: '1.17'
20
76
  type: :development
21
77
  prerelease: false
22
78
  version_requirements: !ruby/object:Gem::Requirement
23
79
  requirements:
24
80
  - - "~>"
25
81
  - !ruby/object:Gem::Version
26
- version: '1.16'
82
+ version: '1.17'
27
83
  - !ruby/object:Gem::Dependency
28
84
  name: rake
29
85
  requirement: !ruby/object:Gem::Requirement
30
86
  requirements:
31
87
  - - "~>"
32
88
  - !ruby/object:Gem::Version
33
- version: '10.0'
89
+ version: '12.3'
34
90
  type: :development
35
91
  prerelease: false
36
92
  version_requirements: !ruby/object:Gem::Requirement
37
93
  requirements:
38
94
  - - "~>"
39
95
  - !ruby/object:Gem::Version
40
- version: '10.0'
96
+ version: '12.3'
41
97
  - !ruby/object:Gem::Dependency
42
- name: rspec
98
+ name: pry
43
99
  requirement: !ruby/object:Gem::Requirement
44
100
  requirements:
45
101
  - - "~>"
46
102
  - !ruby/object:Gem::Version
47
- version: '3.0'
103
+ version: '0.12'
48
104
  type: :development
49
105
  prerelease: false
50
106
  version_requirements: !ruby/object:Gem::Requirement
51
107
  requirements:
52
108
  - - "~>"
53
109
  - !ruby/object:Gem::Version
54
- version: '3.0'
55
- description: Soon
110
+ version: '0.12'
111
+ description: "(in active development) A set of common abstractions"
56
112
  email:
57
113
  - iamdaiver@icloud.com
58
114
  executables: []
@@ -61,7 +117,9 @@ extra_rdoc_files: []
61
117
  files:
62
118
  - ".gitignore"
63
119
  - ".rspec"
120
+ - ".rubocop.yml"
64
121
  - ".travis.yml"
122
+ - CHANGELOG.md
65
123
  - CODE_OF_CONDUCT.md
66
124
  - Gemfile
67
125
  - LICENSE.txt
@@ -70,6 +128,20 @@ files:
70
128
  - bin/console
71
129
  - bin/setup
72
130
  - lib/smart_core.rb
131
+ - lib/smart_core/validator.rb
132
+ - lib/smart_core/validator/attribute.rb
133
+ - lib/smart_core/validator/attribute_set.rb
134
+ - lib/smart_core/validator/command_set.rb
135
+ - lib/smart_core/validator/commands.rb
136
+ - lib/smart_core/validator/commands/add_nested_validations.rb
137
+ - lib/smart_core/validator/commands/add_validation.rb
138
+ - lib/smart_core/validator/commands/base.rb
139
+ - lib/smart_core/validator/commands/validate_with.rb
140
+ - lib/smart_core/validator/commands/work_with_nesteds_mixin.rb
141
+ - lib/smart_core/validator/dsl.rb
142
+ - lib/smart_core/validator/error_set.rb
143
+ - lib/smart_core/validator/exceptions.rb
144
+ - lib/smart_core/validator/invoker.rb
73
145
  - lib/smart_core/version.rb
74
146
  - smart_core.gemspec
75
147
  homepage: https://github.com/0exp/smart_core
@@ -84,16 +156,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
84
156
  requirements:
85
157
  - - ">="
86
158
  - !ruby/object:Gem::Version
87
- version: '0'
159
+ version: 2.3.8
88
160
  required_rubygems_version: !ruby/object:Gem::Requirement
89
161
  requirements:
90
162
  - - ">="
91
163
  - !ruby/object:Gem::Version
92
164
  version: '0'
93
165
  requirements: []
94
- rubyforge_project:
95
- rubygems_version: 2.7.6
166
+ rubygems_version: 3.0.1
96
167
  signing_key:
97
168
  specification_version: 4
98
- summary: Soon
169
+ summary: "(in active development) A set of common abstractions"
99
170
  test_files: []