dry-mutations 0.8.1 → 0.8.8

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