dry-mutations 0.8.1 → 0.8.8

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
  SHA1:
3
- metadata.gz: cb0b6c10516160d2950e96ba5983f671b6b944fc
4
- data.tar.gz: 174d99350fa4138262c39e47bd9280ca7f43a766
3
+ metadata.gz: 3b9a36a11f7ac423bc0a287e56f7955f8e55bfd3
4
+ data.tar.gz: c3e7009846e8dcbfe5c898f8332540fad3d48e93
5
5
  SHA512:
6
- metadata.gz: 0fb7e0849cfe37acb7134a60a0c8669e1a5e3fa730d5324f50c292d3d4c43c4c1056d4c48cece10a3477a9b8b2c688fe87b5c5516b91468c32dc9a0fba14560b
7
- data.tar.gz: dd2e77496437e448325e90283c03f48800d20ad6162c34df17c4f6ec2cbe2728d559d6238551ee9784b4fdc4777ab979a198b58a5289848e497c2837fb1a132f
6
+ metadata.gz: 72eff94bdc14331b0fa44fabc3ee0db5bd04ba2bceca112cedb452a35f04a2a587b39988988f3e3faf38677ffada2d445a1cf719b203d20100747adb3be21b72
7
+ data.tar.gz: 2ec5d6cfa1e2d7668faf340b5c32aa69706d175caa1f1c40d19b5b88daede7f54e25cad3a741b58f9d06b6f3ddd9104109746975d2f1727a1f3c0df95aa10442
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /nbproject/private/
@@ -27,6 +27,7 @@ Style/SignalException:
27
27
  Style/MethodName:
28
28
  Exclude:
29
29
  - 'lib/dry/mutations/utils.rb'
30
+ - 'lib/dry/transactions/*.rb'
30
31
 
31
32
  ################################################################################
32
33
 
@@ -35,9 +36,14 @@ Style/Alias:
35
36
  - 'lib/**/*'
36
37
  - 'spec/**/*'
37
38
 
39
+ Style/Lambda:
40
+ Exclude:
41
+ - 'lib/**/*'
42
+
38
43
  Style/LambdaCall:
39
44
  Exclude:
40
45
  - 'lib/**/*'
46
+ - 'spec/**/*'
41
47
 
42
48
  Style/ParallelAssignment:
43
49
  Exclude:
@@ -1,14 +1,43 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2016-07-08 09:24:30 +0200 using RuboCop version 0.40.0.
3
+ # on 2016-07-13 12:03:55 +0200 using RuboCop version 0.40.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
9
  # Offense count: 1
10
- Style/Documentation:
10
+ # Cop supports --auto-correct.
11
+ # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
12
+ Lint/UnusedMethodArgument:
11
13
  Exclude:
12
- - 'spec/**/*'
13
- - 'test/**/*'
14
- - 'lib/dry/mutations.rb'
14
+ - 'lib/dry/mutations/transactions/dsl.rb'
15
+
16
+ # Offense count: 2
17
+ # Cop supports --auto-correct.
18
+ # Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle, SupportedLastArgumentHashStyles.
19
+ # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
20
+ Style/AlignHash:
21
+ Exclude:
22
+ - 'spec/dry/mutations/command_spec.rb'
23
+
24
+ # Offense count: 1
25
+ # Cop supports --auto-correct.
26
+ # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
27
+ # SupportedStyles: consistent, special_for_inner_method_call, special_for_inner_method_call_in_parentheses
28
+ Style/FirstParameterIndentation:
29
+ Exclude:
30
+ - 'spec/dry/mutations/command_spec.rb'
31
+
32
+ # Offense count: 3
33
+ # Cop supports --auto-correct.
34
+ # Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline.
35
+ # SupportedStyles: single_quotes, double_quotes
36
+ Style/StringLiterals:
37
+ Enabled: false
38
+
39
+ # Offense count: 2
40
+ # Configuration parameters: EnforcedStyle, SupportedStyles.
41
+ # SupportedStyles: snake_case, camelCase
42
+ Style/VariableName:
43
+ Enabled: false
data/Gemfile CHANGED
@@ -4,5 +4,8 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'dry-validation', github: 'dry-rb/dry-validation'
7
+ gem 'dry-monads', github: 'dry-rb/dry-monads'
8
+ gem 'dry-matcher', github: 'dry-rb/dry-matcher'
9
+ gem 'dry-transaction', github: 'dry-rb/dry-transaction'
7
10
 
8
11
  gem 'codeclimate-test-reporter', group: :test, require: nil
data/README.md CHANGED
@@ -30,11 +30,11 @@ Or install it yourself as:
30
30
 
31
31
  ### Enable extensions for the specific mutation’s command
32
32
 
33
- Prepend a `::Dry::Mutations::Command` module to your `Mutation::Command` instance:
33
+ Prepend a `::Dry::Mutations::Extensions::Command` module to your `Mutation::Command` instance:
34
34
 
35
35
  ```ruby
36
36
  class MyMutation < Mutations::Command
37
- prepend ::Dry::Mutations::Command
37
+ prepend ::Dry::Mutations::Extensions::Command
38
38
 
39
39
  required do
40
40
  model :company, class: 'Profile'
@@ -59,7 +59,7 @@ It is possible to mix standard mutations’ syntax with `dry-rb` schemas:
59
59
 
60
60
  ```ruby
61
61
  class MyMutation < Mutations::Command
62
- prepend ::Dry::Mutations::Command
62
+ prepend ::Dry::Mutations::Extensions::Command
63
63
 
64
64
  required do
65
65
  model :company, class: 'Profile'
@@ -93,7 +93,74 @@ required do
93
93
  end
94
94
  ```
95
95
 
96
- ### Turn On Globally (use with caution!)
96
+ ## Dealing `outcome`
97
+
98
+ ### Command
99
+
100
+ ```ruby
101
+ let!(:command) do
102
+ Class.new(::Mutations::Command) do
103
+ prepend ::Dry::Mutations::Extensions::Command
104
+
105
+ required { string :name, max_length: 5 }
106
+ schema { required(:amount).filled(:int?, gt?: 0) }
107
+
108
+ def execute
109
+ @inputs
110
+ end
111
+ end
112
+ end
113
+ ```
114
+
115
+ ### Using `Either` monad
116
+
117
+ ```ruby
118
+ outcome = command.new(name: 'John', amount: 42).run
119
+ outcome.right?
120
+ #⇒ true
121
+ outcome.either.value
122
+ #⇒ { 'name' => 'John', 'amount' => 42 }
123
+
124
+ outcome = command.new(name: 'John Donne', amount: -500).run
125
+ outcome.right?
126
+ #⇒ false
127
+ outcome.left?
128
+ #⇒ true
129
+ outcome.either
130
+ #⇒ Left({
131
+ # "name"=>#<Dry::Mutations::Errors::ErrorAtom:0x00000003b4e7b0
132
+ # @key="name",
133
+ # @symbol=:max_length,
134
+ # @message="size cannot be greater than 5",
135
+ # @index=0,
136
+ # @dry_message=#<Dry::Validation::Message
137
+ # predicate=:max_size?
138
+ # path=[:name]
139
+ # text="size cannot be greater than 5"
140
+ # options={:args=>[5], :rule=>:name, :each=>false}>>,
141
+ # "amount"=>#<Dry::Mutations::Errors::ErrorAtom:0x00000003b4e508
142
+ # @key="amount",
143
+ # @symbol=:gt?,
144
+ # @message="must be greater than 0",
145
+ # @index=1,
146
+ # @dry_message=#<Dry::Validation::Message
147
+ # predicate=:gt?
148
+ # path=[:amount]
149
+ # text="must be greater than 0"
150
+ # options={:args=>[0], :rule=>:amount, :each=>false}>>
151
+ # })
152
+ outcome.either.value
153
+ #⇒ the hash ⇑ above
154
+ ```
155
+
156
+ ### Using `Matcher`
157
+
158
+ ```ruby
159
+ expect(outcome.match { |m| m.success(&:keys) }).to match_array(%w(amount name))
160
+ expect(outcome.match { |m| m.failure(&:keys) }).to be_nil
161
+ ```
162
+
163
+ ## Turn On Globally (use with caution!)
97
164
 
98
165
  ENV['GLOBAL_DRY_MUTATIONS'] = 'true' && rake
99
166
 
@@ -34,6 +34,9 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  spec.add_dependency 'activesupport', '~> 3.2' # prevent mutations to require activesupport 5
36
36
  spec.add_dependency 'mutations', '~> 0.8'
37
- spec.add_dependency 'dry-validation', '~> 0.8'
38
37
  spec.add_dependency 'hashie', '~> 3'
38
+ spec.add_dependency 'dry-validation'
39
+ spec.add_dependency 'dry-matcher'
40
+ spec.add_dependency 'dry-monads'
41
+ spec.add_dependency 'dry-transaction'
39
42
  end
@@ -1,5 +1,9 @@
1
1
  require 'mutations'
2
+
2
3
  require 'dry-validation'
4
+ require 'dry-transaction'
5
+ require 'dry-matcher'
6
+ require 'dry-monads'
3
7
 
4
8
  require 'dry/mutations/version'
5
9
  require 'dry/mutations/utils'
@@ -7,7 +11,8 @@ require 'dry/mutations/monkeypatches'
7
11
  require 'dry/mutations/predicates'
8
12
  require 'dry/mutations/errors'
9
13
  require 'dry/mutations/dsl'
10
- require 'dry/mutations/command'
14
+ require 'dry/mutations/extensions'
15
+ require 'dry/mutations/transactions'
11
16
 
12
17
  module Dry
13
18
  # A dry implementation of mutations interface introduced by
@@ -25,6 +30,6 @@ module Dry
25
30
  end
26
31
 
27
32
  DSL::Types::Nested.extend DSL::Module
28
- ::Mutations::Command.prepend Command if Utils.Truthy?(ENV['GLOBAL_DRY_MUTATIONS'])
33
+ ::Mutations::Command.prepend Extensions::Command if Utils.Truthy?(ENV['GLOBAL_DRY_MUTATIONS'])
29
34
  end
30
35
  end
@@ -15,7 +15,7 @@ module Dry
15
15
  this = is_a?(Class) ? self : self.class
16
16
 
17
17
  parent_with_schema = this.ancestors.drop(1).detect do |klazz|
18
- next if [this, ::Mutations::Command, ::Dry::Mutations::Command].include?(klazz)
18
+ next if [this, ::Mutations::Command, ::Dry::Mutations::Extensions::Command].include?(klazz)
19
19
  klazz.respond_to?(:schema) && klazz.schema.is_a?(Validation::Schema)
20
20
  end
21
21
  parent_with_schema ? Class.new(parent_with_schema.schema.class).new : empty_schema
@@ -0,0 +1,2 @@
1
+ require 'dry/mutations/extensions/command'
2
+ require 'dry/mutations/extensions/outcome'
@@ -0,0 +1,123 @@
1
+ module Dry
2
+ module Mutations
3
+ module Extensions
4
+ module Command # :nodoc:
5
+ def self.prepended base
6
+ fail ArgumentError, "Can not prepend #{self.class} to #{base.class}: base class must be a ::Mutations::Command descendant." unless base < ::Mutations::Command
7
+ base.extend(DSL::Module) unless base.ancestors.include?(DSL::Module)
8
+ base.extend(Module.new do
9
+ def call(*args)
10
+ new(*args).call
11
+ end
12
+
13
+ def to_proc
14
+ ->(*args) { new(*args).call }
15
+ end
16
+
17
+ if base.name && !::Kernel.methods.include?(base_name = base.name.split('::').last.to_sym)
18
+ ::Kernel.class_eval <<-FACTORY, __FILE__, __LINE__ + 1
19
+ def #{base_name}(*args)
20
+ puts "Gonna call [#{base}.call(*args)] with \#{args.inspect}"
21
+ #{base}.call(*args)
22
+ end
23
+ FACTORY
24
+ end
25
+ end)
26
+
27
+ base.singleton_class.prepend(Module.new do
28
+ def respond_to_missing?(method_name, include_private = false)
29
+ [:call, :to_proc].include?(method_name) || super
30
+ end
31
+ end)
32
+ end
33
+
34
+ attr_reader :validation
35
+
36
+ def initialize(*args)
37
+ @raw_inputs = args.inject(Utils.Hash({})) do |h, arg|
38
+ fail ArgumentError.new("All arguments must be hashes. Given: #{args.inspect}.") unless arg.is_a?(Hash)
39
+ h.merge!(arg)
40
+ end
41
+
42
+ @validation_result = schema.(@raw_inputs)
43
+
44
+ @inputs = Utils.Hash @validation_result.output
45
+
46
+ # dry: {:name=>["size cannot be greater than 10"],
47
+ # :properties=>{:first_arg=>["must be a string", "is in invalid format"]},
48
+ # :second_arg=>{:second_sub_arg=>["must be one of: 42"]},
49
+ # :amount=>["must be one of: 42"]}}
50
+ # mut: {:name=>#<Mutations::ErrorAtom:0x00000009534e50 @key=:name, @symbol=:max_length, @message=nil, @index=nil>,
51
+ # :properties=>{
52
+ # :second_arg=>{:second_sub_arg=>#<Mutations::ErrorAtom:0x000000095344a0 @key=:second_sub_arg, @symbol=:in, @message=nil, @index=nil>}
53
+ # :amount=>#<Mutations::ErrorAtom:0x00000009534068 @key=:amount, @symbol=:in, @message=nil, @index=nil>}
54
+
55
+ @errors = Errors::ErrorAtom.patch_message_set(
56
+ Errors::ErrorCompiler.new(schema).(@validation_result.to_ast.last)
57
+ )
58
+
59
+ # Run a custom validation method if supplied:
60
+ validate unless has_errors?
61
+ end
62
+
63
+ ########################################################################
64
+ ### Functional helpers
65
+ ########################################################################
66
+
67
+ def call
68
+ run.either
69
+ end
70
+
71
+ ########################################################################
72
+ ### Overrides
73
+ ########################################################################
74
+
75
+ def validation_outcome(result = nil)
76
+ # Outcome.new(!has_errors?, has_errors? ? nil : result, @errors, @inputs)
77
+ super.tap do |outcome|
78
+ outcome.singleton_class.tap do |klazz|
79
+ klazz.prepend Outcome unless klazz.ancestors.include?(Outcome)
80
+ end
81
+ outcome.eitherify!
82
+ end
83
+ end
84
+
85
+ def execute
86
+ super
87
+ rescue => e
88
+ add_error(:♻, :runtime_exception, e.message)
89
+ end
90
+
91
+ def add_error(key, kind, message = nil, dry_message = nil)
92
+ fail ArgumentError.new("Invalid kind #{kind}") unless kind.is_a?(Symbol)
93
+
94
+ path = key.to_s.split('.')
95
+ # ["#<struct Dry::Validation::Message
96
+ # predicate=:int?,
97
+ # path=[:maturity_set, :maturity_days_set, :days],
98
+ # text=\"must be an integer\",
99
+ # options={:args=>[], :rule=>:days, :each=>false}>"
100
+ dry_message ||= ::Dry::Validation::Message.new(kind, *path.map(&:to_sym), message, rule: :♻)
101
+ atom = Errors::ErrorAtom.new(key, kind, dry_message, message: message)
102
+
103
+ last = path.pop
104
+ (@errors ||= ::Mutations::ErrorHash.new).tap do |errs|
105
+ path.inject(errs) do |cur_errors, part|
106
+ cur_errors[part.to_sym] ||= ::Mutations::ErrorHash.new
107
+ end[last] = atom
108
+ end
109
+ end
110
+
111
+ def messages
112
+ @messages ||= @errors && @errors.values.map(&:dry_message)
113
+ end
114
+
115
+ private
116
+
117
+ def schema
118
+ @schema ||= self.class.schema
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,79 @@
1
+ module Dry
2
+ module Mutations
3
+ module Extensions
4
+ module Outcome # :nodoc:
5
+ include Dry::Monads::Either::Mixin
6
+
7
+ class EitherCalculator # :nodoc:
8
+ include Dry::Monads::Either::Mixin
9
+
10
+ attr_accessor :outcome
11
+
12
+ def calculate
13
+ Right(outcome).bind do |value|
14
+ value.success? ? Right(value.result) : Left(value.errors)
15
+ end
16
+ end
17
+ end
18
+
19
+ class Matcher # :nodoc:
20
+ SUCCESS = Dry::Matcher::Case.new(
21
+ match: ->(value) { value.right? },
22
+ resolve: ->(value) { value.either.value }
23
+ )
24
+
25
+ # rubocop:disable Style/BlockDelimiters
26
+ FAILURE = Dry::Matcher::Case.new(
27
+ match: -> (value, *patterns) {
28
+ value.left? && (patterns.none? || (patterns & value.either.value.keys).any?)
29
+ },
30
+ resolve: -> (value) { value.either.value }
31
+ )
32
+ # rubocop:enable Style/BlockDelimiters
33
+
34
+ # Build the matcher
35
+ def self.!
36
+ Dry::Matcher.new(success: SUCCESS, failure: FAILURE)
37
+ end
38
+
39
+ private_constant :SUCCESS
40
+ private_constant :FAILURE
41
+ end
42
+
43
+ def self.prepended base
44
+ fail ArgumentError, "Can not prepend #{self.class} to #{base.class}: base class must be a ::Mutations::Outcome descendant." unless base < ::Mutations::Outcome
45
+ end
46
+
47
+ attr_reader :either
48
+
49
+ def initialize(is_success, result, errors, inputs)
50
+ super is_success, result, errors, inputs
51
+ etherify!
52
+ end
53
+
54
+ def eitherify!
55
+ calc = EitherCalculator.new
56
+ calc.outcome = self
57
+ @either = calc.calculate
58
+ end
59
+
60
+ def right?
61
+ @either.is_a?(Right)
62
+ end
63
+
64
+ def left?
65
+ @either.is_a?(Left)
66
+ end
67
+
68
+ def value
69
+ @either.value
70
+ end
71
+
72
+ def match
73
+ fail 'Call to Outcome#match requires a block passed.' unless block_given?
74
+ Matcher.!.(self, &Proc.new)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,14 @@
1
+ require 'dry-transaction'
2
+
3
+ require 'dry/mutations/transactions/wrapper'
4
+ require 'dry/mutations/transactions/container'
5
+ require 'dry/mutations/transactions/step_adapters'
6
+ require 'dry/mutations/transactions/dsl'
7
+
8
+ module Dry
9
+ module Mutations # :nodoc:
10
+ def self.Transaction(**params, &cb)
11
+ # ::Dry::Transaction(container: )
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module Dry
2
+ module Mutations
3
+ module Transactions # :nodoc:
4
+ Container = lambda do |whatever|
5
+ whatever.respond_to?(:call) ? whatever : Utils.Constant(whatever).tap do |p|
6
+ fail ArgumentError, "The argument must respond to :call, though #{k.inspect} passed." unless p.respond_to? :call
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ module Dry
2
+ module Mutations
3
+ module Transactions # :nodoc:
4
+ # http://dry-rb.org/gems/dry-transaction/basic-usage/
5
+ # save_user = Dry.Transaction(container: Container) do
6
+ # step :process
7
+ # step :validate
8
+ # step :persist
9
+ # end
10
+ module DSL # :nodoc:
11
+ def chain **params
12
+ return enum_for(:chain) unless block_given? # FIXME: Needed? Works? Remove?
13
+
14
+ λ = Proc.new
15
+
16
+ ::Dry.Transaction(container: ::Dry::Mutations::Transactions::Container, step_adapters: StepAdapters) do
17
+ instance_eval(&λ)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ module Dry
2
+ module Mutations
3
+ module Transactions # :nodoc:
4
+ # http://dry-rb.org/gems/dry-transaction/custom-step-adapters/
5
+ # step adapters must provide a single `#call(step, *args, input)` method,
6
+ # which should return the step’s result wrapped in an `Either` object.
7
+ class StepAdapters < ::Dry::Transaction::StepAdapters # :nodoc:
8
+ class Move # :nodoc:
9
+ def self.inherited(sub)
10
+ name = Utils.Snake(sub, short: true, symbolize: true)
11
+ StepAdapters.register name, sub.new
12
+ adapters[name] = sub
13
+ end
14
+
15
+ def self.adapters
16
+ @adapters ||= Utils.Hash
17
+ end
18
+
19
+ def call(step, *args, input)
20
+ binding.pry unless args.empty? # FIXME
21
+ step.operation.(input)
22
+ end
23
+ end
24
+
25
+ # preload predefined step adapters
26
+ Dir[File.expand_path('step_adapters', __dir__) << '/*'].each do |f|
27
+ require_relative f
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ module Dry
2
+ module Mutations
3
+ module Transactions # :nodoc:
4
+ # http://dry-rb.org/gems/dry-transaction/custom-step-adapters/
5
+ # step adapters must provide a single `#call(step, *args, input)` method,
6
+ # which should return the step’s result wrapped in an `Either` object.
7
+ class Mutate < StepAdapters::Move # :nodoc:
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Dry
2
+ module Mutations
3
+ module Transactions # :nodoc:
4
+ # http://dry-rb.org/gems/dry-transaction/custom-step-adapters/
5
+ # step adapters must provide a single `#call(step, *args, input)` method,
6
+ # which should return the step’s result wrapped in an `Either` object.
7
+ class Transform < StepAdapters::Move # :nodoc:
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Dry
2
+ module Mutations
3
+ module Transactions # :nodoc:
4
+ # http://dry-rb.org/gems/dry-transaction/custom-step-adapters/
5
+ # step adapters must provide a single `#call(step, *args, input)` method,
6
+ # which should return the step’s result wrapped in an `Either` object.
7
+ class Validate < StepAdapters::Move # :nodoc:
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ module Dry
2
+ module Mutations
3
+ module Transactions # :nodoc:
4
+ class Wrapper # :nodoc:
5
+ def initialize(**params)
6
+ (@wrappers = params).each do |name, λ|
7
+ fail ArgumentError, "Wrapper’s constructor requires hash of { name ⇒ λ(proc, value) }" unless name.is_a?(Symbol) && λ.is_a?(Proc)
8
+ singleton_class.send :define_method, name do |value, naked|
9
+ -> { λ.(value, naked.()) }
10
+ end
11
+ end
12
+ end
13
+
14
+ def wrap λ, **params
15
+ end
16
+
17
+ private_class_method :new
18
+ end
19
+
20
+ class Options < Wrapper
21
+ OPTIONS = {
22
+ failure: ->(value, λ) do
23
+ result = λ.()
24
+ Utils.Falsey?(value) ? result : Right(nil)
25
+ end
26
+ }.freeze
27
+ def initialize
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,8 +1,8 @@
1
1
  module Dry
2
2
  module Mutations
3
3
  module Utils # :nodoc:
4
- FALSEY = /\A#{Regexp.union(%w(0 false falsey no n)).source}\z/i
5
- TRUTHY = /\A#{Regexp.union(%w(1 true truthy yes y)).source}\z/i
4
+ FALSEY = /\A#{Regexp.union(%w(0 skip false falsey no n)).source}\z/i
5
+ TRUTHY = /\A#{Regexp.union(%w(1 use true truthy yes y)).source}\z/i
6
6
 
7
7
  def self.Falsey? input, explicit: true
8
8
  explicit ? input.to_s =~ FALSEY : input.to_s !~ TRUTHY
@@ -12,6 +12,43 @@ module Dry
12
12
  explicit ? input.to_s =~ TRUTHY : input.to_s !~ FALSEY
13
13
  end
14
14
 
15
+ def self.Snake(whatever, short: false, symbolize: false)
16
+ result = whatever.to_s.split('::').map do |e|
17
+ e.gsub(/(?<=[^\W_])(?=[A-Z])/, '_').downcase
18
+ end
19
+ result = short ? result.last : result.join('__')
20
+ symbolize ? result.to_sym : result
21
+ end
22
+
23
+ def self.SnakeSafe(whatever, existing = [], update_existing: true, short: false, symbolize: false)
24
+ result = Snake(whatever, short: short)
25
+ str = loop do
26
+ break result unless existing.include? result
27
+ suffix = result[/(?<=_)\d+(?=\z)/]
28
+ suffix.nil? ? result << '_1' : result[-suffix.length..-1] = (suffix.to_i + 1).to_s
29
+ end.tap { |r| existing << r if update_existing }
30
+ symbolize ? str.to_sym : str
31
+ end
32
+
33
+ def self.Camel(whatever)
34
+ whatever.to_s.split('__').map do |s|
35
+ s.gsub(/(?:\A|_)(?<letter>\w)/) { $~[:letter].upcase }
36
+ end.join('::')
37
+ end
38
+
39
+ def self.Constant(whatever)
40
+ ::Kernel.const_get(Camel(whatever))
41
+ end
42
+
43
+ def self.Λ input, **params
44
+ case
45
+ when params[:method] then input.method(params.delete[:method].to_sym).to_proc
46
+ when input.respond_to?(:to_proc) then input.to_proc
47
+ when input.respond_to?(:call) then input.method(:call).to_proc
48
+ else fail ArgumentError, "The executor given can not be executed (forgot to specify :method param?)"
49
+ end
50
+ end
51
+
15
52
  # Lazy detector for Hashie::Mash
16
53
  # TODO: Make it possible to choose friendly hash implementation
17
54
  USE_HASHIE_MASH = Falsey?(ENV['PLAIN_HASHES'], explicit: false) && begin
@@ -28,7 +65,7 @@ module Dry
28
65
  # Converts a hash to a best available hash implementation
29
66
  # with stringified keys, since `Mutations` expect hash
30
67
  # keys to be strings.
31
- def self.Hash hash
68
+ def self.Hash hash = {}
32
69
  case
33
70
  when USE_HASHIE_MASH
34
71
  Kernel.const_get('::Hashie::Mash').new(hash)
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Mutations
3
- VERSION = '0.8.1'.freeze
3
+ VERSION = '0.8.8'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-mutations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aleksei Matiushkin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-11 00:00:00.000000000 Z
11
+ date: 2016-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -109,33 +109,75 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0.8'
111
111
  - !ruby/object:Gem::Dependency
112
- name: dry-validation
112
+ name: hashie
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0.8'
117
+ version: '3'
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0.8'
124
+ version: '3'
125
125
  - !ruby/object:Gem::Dependency
126
- name: hashie
126
+ name: dry-validation
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - "~>"
129
+ - - ">="
130
130
  - !ruby/object:Gem::Version
131
- version: '3'
131
+ version: '0'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - "~>"
136
+ - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: '3'
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: dry-matcher
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: dry-monads
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: dry-transaction
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
139
181
  description: |2
140
182
  Mutations gem interface implemented with `dry-rb`’s validation schemas.
141
183
  email:
@@ -162,7 +204,6 @@ files:
162
204
  - config/messages.yml
163
205
  - dry-mutations.gemspec
164
206
  - lib/dry/mutations.rb
165
- - lib/dry/mutations/command.rb
166
207
  - lib/dry/mutations/dsl.rb
167
208
  - lib/dry/mutations/dsl/blocks.rb
168
209
  - lib/dry/mutations/dsl/module.rb
@@ -172,8 +213,19 @@ files:
172
213
  - lib/dry/mutations/errors.rb
173
214
  - lib/dry/mutations/errors/error_atom.rb
174
215
  - lib/dry/mutations/errors/error_compiler.rb
216
+ - lib/dry/mutations/extensions.rb
217
+ - lib/dry/mutations/extensions/command.rb
218
+ - lib/dry/mutations/extensions/outcome.rb
175
219
  - lib/dry/mutations/monkeypatches.rb
176
220
  - lib/dry/mutations/predicates.rb
221
+ - lib/dry/mutations/transactions.rb
222
+ - lib/dry/mutations/transactions/container.rb
223
+ - lib/dry/mutations/transactions/dsl.rb
224
+ - lib/dry/mutations/transactions/step_adapters.rb
225
+ - lib/dry/mutations/transactions/step_adapters/mutate.rb
226
+ - lib/dry/mutations/transactions/step_adapters/transform.rb
227
+ - lib/dry/mutations/transactions/step_adapters/validate.rb
228
+ - lib/dry/mutations/transactions/wrapper.rb
177
229
  - lib/dry/mutations/utils.rb
178
230
  - lib/dry/mutations/version.rb
179
231
  homepage: http://github.com/am-kantox/dry-mutations
@@ -1,49 +0,0 @@
1
- module Dry
2
- module Mutations
3
- module Command # :nodoc:
4
- def self.prepended base
5
- fail ArgumentError, "Can not prepend #{self.class} to #{base.class}: base class must be a ::Mutations::Command descendant." unless base < ::Mutations::Command
6
- base.extend(DSL::Module) unless base.ancestors.include?(DSL::Module)
7
- end
8
-
9
- attr_reader :validation
10
-
11
- def initialize(*args)
12
- @raw_inputs = args.inject(Utils.Hash({})) do |h, arg|
13
- fail ArgumentError.new('All arguments must be hashes') unless arg.is_a?(Hash)
14
- h.merge!(arg)
15
- end
16
-
17
- @validation_result = schema.(@raw_inputs)
18
-
19
- @inputs = Utils.Hash @validation_result.output
20
-
21
- # dry: {:name=>["size cannot be greater than 10"],
22
- # :properties=>{:first_arg=>["must be a string", "is in invalid format"]},
23
- # :second_arg=>{:second_sub_arg=>["must be one of: 42"]},
24
- # :amount=>["must be one of: 42"]}}
25
- # mut: {:name=>#<Mutations::ErrorAtom:0x00000009534e50 @key=:name, @symbol=:max_length, @message=nil, @index=nil>,
26
- # :properties=>{
27
- # :second_arg=>{:second_sub_arg=>#<Mutations::ErrorAtom:0x000000095344a0 @key=:second_sub_arg, @symbol=:in, @message=nil, @index=nil>}
28
- # :amount=>#<Mutations::ErrorAtom:0x00000009534068 @key=:amount, @symbol=:in, @message=nil, @index=nil>}
29
-
30
- @errors = Errors::ErrorAtom.patch_message_set(
31
- Errors::ErrorCompiler.new(schema).(@validation_result.to_ast.last)
32
- )
33
-
34
- # Run a custom validation method if supplied:
35
- validate unless has_errors?
36
- end
37
-
38
- def messages
39
- @messages ||= @errors && @errors.values.map(&:dry_message)
40
- end
41
-
42
- private
43
-
44
- def schema
45
- @schema ||= self.class.schema
46
- end
47
- end
48
- end
49
- end