luna_park 0.11.2 → 0.11.3

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: bc89904bfac7a26a4898141e26a1ec059fdc19a5de2adc7dca5c54e58e04bee8
4
- data.tar.gz: 142b00eeb4fee4f412b9a811e196104b4522a1354a95b5e58dbc766b7e3e8a3c
3
+ metadata.gz: bdf7e8b655b452fed676d668734e7ff9aa9ce6acab8fba29db0e07bd22b9b24d
4
+ data.tar.gz: 55adce683441034c0b47f1b1f93e45c7d562e7d965ce800eb73e72e71304d73c
5
5
  SHA512:
6
- metadata.gz: 8443e5c9f90f0a80647eebe61453e21a0de3130bd16147171dd19ed4df25217b8f8b85d09754b3105b82e3a51485f9fbb6c5726838705f3ba03363fb28fd01fb
7
- data.tar.gz: a6c102273edc954cd156d9f68cc250a22c0ef272d89650a1a84779724e43b507ed8fa40b5704096152efdcc0b6abf2bf29a837de028ac5b9919c3d7905d75eb8
6
+ metadata.gz: 49af03152d02807005c2b308f67d4ce5553762a15608e5a562c5c024ede6ccdc0526c9890e55ab4095302bb80e29b86ae5066b39d43892121ec65b6339c1f744
7
+ data.tar.gz: 03ab2f5cc9c4c4b945ad0f50a39e841a08b5ab1ecb137b7cbba1ee099270765e21fb63b391ec867bba6eaee5d82d207479b39e04bdc15a36fd62c74860f2cb80
data/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.11.3] - 2021-09-02
8
+ Added
9
+ - new mapper `Mappers::Codirectionsl` with DSL
10
+
7
11
  ## [0.11.2] - 2021-09-01
8
12
  Added
9
13
  - Github CI
data/Gemfile.lock CHANGED
@@ -6,7 +6,7 @@ PATH
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- addressable (2.7.0)
9
+ addressable (2.8.0)
10
10
  public_suffix (>= 2.0.2, < 5.0)
11
11
  ast (2.4.1)
12
12
  bugsnag (6.13.1)
@@ -95,7 +95,7 @@ GEM
95
95
  pry-byebug (3.9.0)
96
96
  byebug (~> 11.0)
97
97
  pry (~> 0.13.0)
98
- public_suffix (4.0.5)
98
+ public_suffix (4.0.6)
99
99
  rainbow (3.0.0)
100
100
  rake (13.0.1)
101
101
  regexp_parser (1.7.1)
@@ -4,7 +4,6 @@ module LunaPark
4
4
  module Extensions
5
5
  module Exceptions
6
6
  # class-level mixin
7
-
8
7
  module Substitutive
9
8
  def self.extended(base)
10
9
  base.extend ClassMethods
@@ -48,7 +48,7 @@ module LunaPark
48
48
  # use_case.dependencies[:messenger] # => #<Proc:0x0000564a0d90d438@t.rb:34>
49
49
  # use_case.dependencies.call_with_cache(:messenger) # => 'Foobar'
50
50
  def call_with_cache(key)
51
- cache.key?(key) ? cache[key] : cache[key] = self.fetch(key).call
51
+ cache.key?(key) ? cache[key] : cache[key] = fetch(key).call
52
52
  end
53
53
 
54
54
  def []=(key, _val)
@@ -126,7 +126,7 @@ module LunaPark
126
126
  def dependency(name, &block)
127
127
  raise ArgumentError, 'no block given' unless block_given?
128
128
 
129
- self.dependencies[name] = block
129
+ dependencies[name] = block
130
130
 
131
131
  define_method(name) do
132
132
  dependencies.call_with_cache(name)
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LunaPark
4
+ module Mappers
5
+ class Codirectional < Simple
6
+ module Copyists
7
+ # Copyist for copiyng value between two schemas with DIFFERENT or NESTED paths
8
+ # (Works with only one described attribute)
9
+ class Nested
10
+ def initialize(attrs_path:, row_path:)
11
+ @attrs_path = attrs_path
12
+ @row_path = row_path
13
+
14
+ raise ArgumentError, 'attr path can not be nil' if attrs_path.nil?
15
+ raise ArgumentError, 'store path can not be nil' if row_path.nil?
16
+ end
17
+
18
+ def from_row(row:, attrs:)
19
+ copy_nested(from: row, to: attrs, from_path: @row_path, to_path: @attrs_path)
20
+ end
21
+
22
+ def to_row(row:, attrs:)
23
+ copy_nested(from: attrs, to: row, from_path: @attrs_path, to_path: @row_path)
24
+ end
25
+
26
+ private
27
+
28
+ def copy_nested(from:, to:, from_path:, to_path:)
29
+ value = read(from, from_path)
30
+
31
+ return if value == Undefined # omit undefined keys
32
+
33
+ write(to, to_path, value)
34
+ end
35
+
36
+ def read(from, from_path)
37
+ if from_path.is_a?(Array) # when given `%i[key path]` - not just `:key`
38
+ read_nested(from, path: from_path)
39
+ else # when given just `:key`
40
+ read_plain(from, key: from_path)
41
+ end
42
+ end
43
+
44
+ def write(to, to_path, value)
45
+ if to_path.is_a?(Array) # when given `%i[key path]` - not just `:key`
46
+ write_nested(to, to_path, value)
47
+ else # when given just `:key`
48
+ to[to_path] = value
49
+ end
50
+ end
51
+
52
+ def read_nested(from, path:)
53
+ *path_to_head, head_key = path # split `[:a, :b, :c]` to `[:a, :b]` and `:c`
54
+ head_hash = from.dig(*path_to_head) # from `{a: {b: {c: 'value'}}}` get `{c: 'value'}`
55
+
56
+ return Undefined if head_hash.nil? # when there are no key at the path `[:a, :b]`
57
+ return Undefined unless head_hash.key?(head_key) # when there are no key at the path `[:a, :b, :c]`
58
+
59
+ head_hash[head_key] # get 'value' from from `{c: 'value'}` stored at `{a: {b: {c: 'value'}}}`
60
+ end
61
+
62
+ def read_plain(from, key:)
63
+ from.key?(key) ? from[key] : Undefined
64
+ end
65
+
66
+ def write_nested(hash, full_path, value)
67
+ *tail_path, head_key = full_path
68
+ build_nested_hash(hash, tail_path)[head_key] = value
69
+ end
70
+
71
+ #
72
+ # @example
73
+ # hash = { a: { x: 'x' } }
74
+ # build_nested_hash(hash, [:a, :b, :c]) # => {} # (returns new hash at path [:a, :b, :c])
75
+ # hash # => { a: { b: { c: {} }, x: 'x' } }
76
+ #
77
+ # @example
78
+ # hash = { a: { x: 'x' } }
79
+ # build_nested_hash(hash, [:a, :b, :c])[:d] = 'value'
80
+ # hash # => { a: { b: { c: { d: 'value' } }, x: 'x' } }
81
+ def build_nested_hash(nested_hash, path)
82
+ path.inject(nested_hash) { |output, key| output[key] ||= {} }
83
+ end
84
+
85
+ class Undefined; end
86
+
87
+ private_constant :Undefined
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LunaPark
4
+ module Mappers
5
+ class Codirectional < Simple
6
+ module Copyists
7
+ # Copyist for copiyng value between two schemas with SAME and PLAIN paths
8
+ class Slice
9
+ def initialize
10
+ @keys = []
11
+ end
12
+
13
+ def add_key(key)
14
+ @keys << key
15
+ end
16
+
17
+ def from_row(row:, attrs:)
18
+ attrs.merge! row.slice(*@keys)
19
+ end
20
+
21
+ def to_row(row:, attrs:)
22
+ row.merge! attrs.slice(*@keys)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'luna_park/mappers/simple'
4
+ require 'luna_park/mappers/codirectional/copyists/slice'
5
+ require 'luna_park/mappers/codirectional/copyists/nested'
6
+
7
+ module LunaPark
8
+ module Mappers
9
+ ##
10
+ # DSL for describe Nested Schema translation: entity attributes to database row and vice-versa
11
+ #
12
+ # @example
13
+ # class Mappers::Transaction < LunaPark::Mappers::Codirectional
14
+ # map attr: :uid, row: :id
15
+ # map attr: [:charge, :amount], row: :charge_amount
16
+ # map attr: [:charge, :currency], row: :charge_currency # using aliased args
17
+ # map :comment
18
+ # end
19
+ #
20
+ # mapper = Mappers::Transaction
21
+ #
22
+ # attrs = { charge: { amount: 10, currency: 'USD' }, comment: 'Foobar' }
23
+ # transaction = Entities::Transaction.new(attrs)
24
+ #
25
+ # # Mapper transforms attr attributes to database row and vice-verse
26
+ # row = mapper.to_row(transaction) # => { charge_amount: 10, charge_currency: 'USD', comment: 'Foobar' }
27
+ # new_row = sequel_database_table.insert(row) # => { id: 42, charge_amount: 10, charge_currency: 'USD', comment: 'Foobar' }
28
+ # new_attrs = mapper.from_row(new_row) # => { uid: 42, charge: { amount: 10, currency: 'USD' }, comment: 'Foobar' }
29
+ #
30
+ # transaction.set_attributes(new_attrs)
31
+ # transaction.to_h # => { uid: 42, charge: { amount: 10, currency: 'USD' }, comment: 'Foobar' }
32
+ class Codirectional < Simple
33
+ class << self
34
+ ##
35
+ # Describe translation between two schemas: attr and table
36
+ #
37
+ # @example
38
+ # class Mappers::Transaction < LunaPark::Mappers::Codirectional
39
+ # map attr: :id, row: :uid
40
+ # map attr: [:charge, :amount], row: :charge_amount
41
+ # map :comment
42
+ # end
43
+ #
44
+ # Mappers::Transaction.from_row({ id: 1, charge_amount: 2 }) # => { uid: 1, charge: { amount: 2 } }
45
+ # Mappers::Transaction.to_row({ uid: 1, charge: { amount: 2 } }) # => { id: 1, charge_amount: 2 }
46
+ def map(*common_keys, attr: nil, row: nil)
47
+ attrs(*common_keys) if common_keys.any?
48
+
49
+ self.attr attr, row: row if attr
50
+ end
51
+
52
+ # @example
53
+ # class Mappers::Transaction < LunaPark::Mappers::Codirectional
54
+ # attr :uid, row: :id
55
+ # attr %i[charge amount], row: :charge_amount
56
+ # end
57
+ def attr(attr, row: nil)
58
+ return attrs(attr) if row.nil?
59
+
60
+ attr_path = to_path(attr)
61
+ row_path = to_path(row)
62
+
63
+ if attr_path == row_path
64
+ attrs(attr_path)
65
+ else
66
+ nested_copyists << Copyists::Nested.new(attrs_path: attr_path, row_path: row_path)
67
+ end
68
+ end
69
+
70
+ # @example
71
+ # class Mappers::Transaction < LunaPark::Mappers::Codirectional
72
+ # attrs :comment, :uid, %i[addresses home], :created_at
73
+ # end
74
+ def attrs(*common_keys)
75
+ common_keys.each do |common_key|
76
+ path = to_path(common_key)
77
+ if path.is_a?(Array)
78
+ nested_copyists << Copyists::Nested.new(attrs_path: path, row_path: path)
79
+ else
80
+ slice_copyist.add_key(path)
81
+ end
82
+ end
83
+ end
84
+
85
+ def from_row(input)
86
+ row = input.to_h
87
+ {}.tap do |attrs|
88
+ slice_copyist.from_row(row: row, attrs: attrs)
89
+ nested_copyists.each { |copyist| copyist.from_row(row: row, attrs: attrs) }
90
+ end
91
+ end
92
+
93
+ def to_row(input)
94
+ attrs = input.to_h
95
+ {}.tap do |row|
96
+ slice_copyist.to_row(row: row, attrs: attrs)
97
+ nested_copyists.each { |copyist| copyist.to_row(row: row, attrs: attrs) }
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ # @example
104
+ # to_path :email # => :email
105
+ # to_path ['email'] # => :email
106
+ # to_path [:charge, 'amount'] # => [:charge, :amount]
107
+ def to_path(input, full: input)
108
+ case input
109
+ when Symbol then input
110
+ when String then input.to_sym
111
+ when Array
112
+ return to_path(input.first, full: full) if input.size <= 1
113
+
114
+ input.flat_map { |elem| to_path(elem, full: full) }
115
+ else raise ArgumentError, "Unexpected path part `#{input.inspect}` in `#{full.inspect}`. " \
116
+ 'Expected Symbol, String or Array'
117
+ end
118
+ end
119
+
120
+ def slice_copyist
121
+ @slice_copyist ||= Copyists::Slice.new
122
+ end
123
+
124
+ def nested_copyists
125
+ @nested_copyists ||= []
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'luna_park/errors'
4
+
5
+ module LunaPark
6
+ module Mappers
7
+ module Errors
8
+ class NotArray < LunaPark::Errors::System
9
+ message { |d| "input MUST be an Array, but given #{d[:input].class} `#{d[:input].inspect}`" }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'luna_park/errors'
3
+ require 'luna_park/mappers/errors'
4
4
 
5
5
  module LunaPark
6
6
  module Mappers
7
+ ##
7
8
  # Abstract mapper for transform data from Entity attributes schema to Database row schema
9
+ #
8
10
  # @example
9
11
  # class TransactionMapper < LunaPark::Mappers::Simple
10
12
  # def self.from_row(row)
@@ -65,6 +67,7 @@ module LunaPark
65
67
  # Transforms array of rows to array of attribute hashes
66
68
  def from_rows(rows)
67
69
  return [] if rows.nil?
70
+ raise Errors::NotArray.new(input: rows) unless rows.is_a?(Array)
68
71
 
69
72
  rows.to_a.map { |hash| from_row(hash) }
70
73
  end
@@ -73,18 +76,19 @@ module LunaPark
73
76
  # Transforms array of attribute hashes to array of rows
74
77
  def to_rows(attr_hashes)
75
78
  return [] if attr_hashes.nil?
79
+ raise Errors::NotArray.new(input: attr_hashes) unless attr_hashes.is_a?(Array)
76
80
 
77
81
  attr_hashes.to_a.map { |entity| to_row(entity) }
78
82
  end
79
83
 
80
84
  # @abstract
81
85
  def from_row(_row)
82
- raise Errors::AbstractMethod
86
+ raise LunaPark::Errors::AbstractMethod
83
87
  end
84
88
 
85
89
  # @abstract
86
90
  def to_row(_attrs)
87
- raise Errors::AbstractMethod
91
+ raise LunaPark::Errors::AbstractMethod
88
92
  end
89
93
  end
90
94
  end
@@ -252,6 +252,7 @@ module LunaPark
252
252
  end
253
253
 
254
254
  alias failure? fail?
255
+ alias failed? fail?
255
256
 
256
257
  # @return [Boolean] true if the scenario runs successfully
257
258
  def success?
@@ -318,7 +319,7 @@ module LunaPark
318
319
  end
319
320
 
320
321
  def on_raise(error)
321
- raise error.cover_up_backtrace
322
+ raise error
322
323
  end
323
324
  end
324
325
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LunaPark
4
- VERSION = '0.11.2'
4
+ VERSION = '0.11.3'
5
5
  end
data/lib/luna_park.rb CHANGED
@@ -67,6 +67,7 @@ require 'luna_park/values/compound'
67
67
  require 'luna_park/values/single'
68
68
  require 'luna_park/values/attributable'
69
69
  require 'luna_park/mappers/simple'
70
+ require 'luna_park/mappers/codirectional'
70
71
  require 'luna_park/repository'
71
72
  require 'luna_park/repositories/sequel'
72
73
  require 'luna_park/repositories/postgres'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: luna_park
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.2
4
+ version: 0.11.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Kudrin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-09-01 00:00:00.000000000 Z
12
+ date: 2021-09-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bugsnag
@@ -368,6 +368,10 @@ files:
368
368
  - lib/luna_park/http/request.rb
369
369
  - lib/luna_park/http/response.rb
370
370
  - lib/luna_park/http/send.rb
371
+ - lib/luna_park/mappers/codirectional.rb
372
+ - lib/luna_park/mappers/codirectional/copyists/nested.rb
373
+ - lib/luna_park/mappers/codirectional/copyists/slice.rb
374
+ - lib/luna_park/mappers/errors.rb
371
375
  - lib/luna_park/mappers/simple.rb
372
376
  - lib/luna_park/notifiers/bugsnag.rb
373
377
  - lib/luna_park/notifiers/log.rb