luna_park 0.12.1 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +26 -3
- data/Gemfile.lock +1 -1
- data/lib/luna_park/extensions/data_mapper.rb +270 -38
- data/lib/luna_park/extensions/repositories/postgres/create.rb +8 -2
- data/lib/luna_park/extensions/repositories/postgres/delete.rb +1 -1
- data/lib/luna_park/extensions/repositories/postgres/read.rb +22 -31
- data/lib/luna_park/extensions/repositories/postgres/update.rb +6 -3
- data/lib/luna_park/mapper.rb +7 -0
- data/lib/luna_park/mappers/codirectional/copyists/nested.rb +25 -4
- data/lib/luna_park/mappers/codirectional.rb +10 -15
- data/lib/luna_park/mappers/errors.rb +1 -1
- data/lib/luna_park/mappers/simple.rb +11 -9
- data/lib/luna_park/notifiers/tagged_log/tagged_formatter.rb +5 -5
- data/lib/luna_park/notifiers/tagged_log.rb +1 -1
- data/lib/luna_park/version.rb +1 -1
- data/lib/luna_park.rb +1 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3491b1852cab029d5f1b60cdc9ff638e2eac71a1d366e1463e31b7c6a4afa127
|
4
|
+
data.tar.gz: f0b5abc0af74a11ac5d6e392def3daa5e565ac7ba5033315981013dae3f0734a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25f821e41e63fcb3bfcc8744ea203b861360126cc7edc12f5032e7153831a514e527ef80e8453fc5b8f24e5bf5f458c88226f1103ab8bcf85249222d4c7355b5
|
7
|
+
data.tar.gz: 903dd8923a134ea14d8f3611e3c803989f976f923e5b251cc65e47bef6cecc923173ae6654f2361ac2cc49190231d7d24c427d4144720abdbd9d0368b5ae386b
|
data/.rubocop.yml
CHANGED
@@ -50,7 +50,7 @@ Style/Documentation:
|
|
50
50
|
Enabled: false
|
51
51
|
|
52
52
|
Naming/MethodParameterName:
|
53
|
-
AllowedNames: io, id, to, by, on, in, at, ip, db, pk, fk
|
53
|
+
AllowedNames: io, id, to, by, on, in, at, ip, db, pk, fk, ds
|
54
54
|
|
55
55
|
# TODO: поговорить с Филиппом про attr
|
56
56
|
Layout/EmptyLinesAroundAttributeAccessor:
|
data/CHANGELOG.md
CHANGED
@@ -4,13 +4,36 @@ 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.
|
7
|
+
## [0.13.0] - 2023-06-06
|
8
8
|
Changed
|
9
|
+
- `Extensions::Repositories::Postgres::Delete` returns boolean
|
10
|
+
- DataMapper became more safe: it will raise `MoreThanOneRecord` if `#read_one` received array with multiple items,
|
11
|
+
so it will show you your critical logic mistake
|
12
|
+
|
13
|
+
Added
|
14
|
+
- `DataMapper.mapper` now can receive block instead of class, to describe anonymous mapper;
|
15
|
+
default parent can be customized by defining `base_anonymous_mapper` method
|
16
|
+
- `DataMapper.entity` now have second argument to customize `coercion` - you can use your own Entity class (even Dry Struct)
|
17
|
+
- DataMapper became more safe: it will not try to transform hash of attributes to array of pairs
|
18
|
+
- Postgres extension `Create` now will set `updated_at` and `created_at` if exists
|
19
|
+
- Postgres extension `Update` now will set `updated_at` if exists
|
20
|
+
- Postgres extension `Read` now have default `#scope` abstract method to handle scoping options
|
21
|
+
- Postgres extension `Read` now have `#transaction` method
|
22
|
+
- Postgres extension `Read` now have `#lock(pk, &block)` method and `#lock!(pk, &block)`
|
23
|
+
- each Repository NotFound exception now have its own exception class, inherited from common NotFound exception
|
24
|
+
- `LunaPark::Mappers::Codirectional` now available as default mapper `LunaPark::Mapper`
|
25
|
+
- `LunaPark::Mappers::Codirectional` now can have nested mappers, and can be configured for arrays
|
26
|
+
|
27
|
+
Fixed
|
28
|
+
- DataMapper configuration (entity, mapper) now can be inherited. NotFound error also will be inherited.
|
29
|
+
|
30
|
+
## [0.12.0] - 2023-02-23
|
31
|
+
Added
|
9
32
|
- Added `TaggedLog`
|
10
33
|
|
11
34
|
## [0.11.7] - 2022-10-07
|
12
35
|
Changed
|
13
|
-
- Added `formatter` to `Notifiers::Log`.
|
36
|
+
- Added `formatter` to `Notifiers::Log`. Usage of `format` in initializer is now deprecated.
|
14
37
|
|
15
38
|
## [0.11.6] - 2021-10-06
|
16
39
|
Changed
|
@@ -21,7 +44,7 @@ Added
|
|
21
44
|
- add short alias for exceptions (`i18n:` instead of `i18n_key:`)
|
22
45
|
|
23
46
|
## [0.11.5] - 2022-09-27
|
24
|
-
|
47
|
+
Added
|
25
48
|
- Added `.custom_error` method to `Extensions::HasError` to define errors with a custom superclass
|
26
49
|
- Added `#inject` method to `Extensions::Injector` - dependencies setter that allows you to create method chains
|
27
50
|
|
data/Gemfile.lock
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'luna_park/mappers/simple'
|
4
|
+
|
3
5
|
module LunaPark
|
4
6
|
module Extensions
|
5
7
|
# @example
|
@@ -20,7 +22,7 @@ module LunaPark
|
|
20
22
|
# def save(input)
|
21
23
|
# entity = wrap(input)
|
22
24
|
# row = to_row(entity)
|
23
|
-
# new_row = products.where(id: entity.id).update(row)
|
25
|
+
# new_row = products.where(id: entity.id).returning.update(row)
|
24
26
|
# new_attrs = from_row(new_row)
|
25
27
|
# entity.set_attributes(new_attrs)
|
26
28
|
# entity
|
@@ -36,33 +38,174 @@ module LunaPark
|
|
36
38
|
# alias products dataset
|
37
39
|
# end
|
38
40
|
module DataMapper
|
39
|
-
|
40
|
-
base
|
41
|
-
|
41
|
+
class << self
|
42
|
+
def extended(base)
|
43
|
+
base.include self
|
44
|
+
end
|
45
|
+
|
46
|
+
def included(base)
|
47
|
+
base.extend ClassMethods
|
48
|
+
base.include InstanceMethods
|
49
|
+
|
50
|
+
base.__define_constants__
|
51
|
+
|
52
|
+
defaults(base)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def defaults(base)
|
58
|
+
base.entity OpenStruct, :new
|
59
|
+
base.mapper LunaPark::Mappers::Simple
|
60
|
+
end
|
42
61
|
end
|
43
62
|
|
44
63
|
module ClassMethods
|
45
|
-
attr_reader :entity_class, :mapper_class
|
64
|
+
attr_reader :entity_class, :mapper_class, :__entity_coercion__
|
46
65
|
|
47
66
|
# Configure repository
|
48
67
|
|
49
|
-
|
50
|
-
|
68
|
+
# Configure tagret entity class and coercion for it
|
69
|
+
#
|
70
|
+
# @example default coercion for entity type than responds to .call
|
71
|
+
# class MyRepository
|
72
|
+
# entity MyEntity
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# input = { foo: 'FOO', bar: 'BAR' }
|
76
|
+
# MyRepository.new.send(:wrap, input) == MyEntity.call(input)
|
77
|
+
#
|
78
|
+
# @example default coercion for entity type than responds to .wrap
|
79
|
+
# class MyRepository
|
80
|
+
# entity MyEntity
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# input = { foo: 'FOO', bar: 'BAR' }
|
84
|
+
# MyRepository.new.send(:wrap, input) == MyEntity.wrap(input)
|
85
|
+
#
|
86
|
+
# @example custom coercion by symbol, for entity type than responds to described_method
|
87
|
+
# class MyRepository
|
88
|
+
# entity MyEntity, :build
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# input = { foo: 'FOO', bar: 'BAR' }
|
92
|
+
# MyRepository.new.send(:wrap, input) == MyEntity.build(input)
|
93
|
+
#
|
94
|
+
# @example custom coercion by callable object
|
95
|
+
# class MyRepository
|
96
|
+
# entity MyEntity, BUILD_ENTITY
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# input = { foo: 'FOO', bar: 'BAR' }
|
100
|
+
# MyRepository.new.send(:wrap, input) == BUILD_ENTITY(input)
|
101
|
+
def entity(entity, coercion = nil)
|
102
|
+
@entity_class = entity
|
103
|
+
@__entity_coercion__ = __build_entity_coercion__(coercion)
|
104
|
+
@entity_class
|
105
|
+
end
|
106
|
+
|
107
|
+
# Configure Mapper
|
108
|
+
#
|
109
|
+
# @example With anonymous mapper
|
110
|
+
# class Repository < LunaPark::Repository
|
111
|
+
# mapper do
|
112
|
+
# attr :foo, row: :fuu
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# Repository.mapper_class.to_row(foo: 'Foo') # => { fuu: 'Foo' }
|
117
|
+
#
|
118
|
+
# @example With mapper class
|
119
|
+
# class Repository::Mapper < LunaPark::Mapper
|
120
|
+
# attr :foo, row: :fuu
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# class Repository < LunaPark::Repository
|
124
|
+
# mapper Mapper
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# Repository.new.mapper_class.to_row(foo: 'Foo') # => { fuu: 'Foo' }
|
128
|
+
#
|
129
|
+
# @example Without mapper
|
130
|
+
# class Repository < LunaPark::Repository
|
131
|
+
# def example_to_row(attrs)
|
132
|
+
# to_row attrs
|
133
|
+
# end
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# Repository.new.example_to_row(foo: 'Foo') # => { foo: 'Foo' }
|
137
|
+
#
|
138
|
+
def mapper(mapper = Undefined, &block)
|
139
|
+
raise ArgumentError, 'Expected mapper xOR block' unless (mapper == Undefined) ^ block.nil?
|
140
|
+
|
141
|
+
return @mapper_class = mapper if block.nil?
|
142
|
+
|
143
|
+
@mapper_class = Class.new(base_anonymous_mapper)
|
144
|
+
@mapper_class.class_eval(&block)
|
145
|
+
@mapper_class
|
146
|
+
end
|
147
|
+
|
148
|
+
def __build_entity_coercion__(coercion) # rubocop:disable Metrics/AbcSize
|
149
|
+
return entity_class.method(coercion) if coercion.is_a? Symbol
|
150
|
+
return coercion if coercion.respond_to?(:call)
|
151
|
+
|
152
|
+
raise ArgumentError, 'coercion MUST be call\'able, Symbol or nil' unless coercion.nil?
|
153
|
+
|
154
|
+
infer_entity_coercion
|
155
|
+
end
|
156
|
+
|
157
|
+
def infer_entity_coercion # rubocop:disable Metrics/AbcSize
|
158
|
+
return entity_class.method(:call) if entity_class.respond_to?(:call)
|
159
|
+
return entity_class.method(:wrap) if entity_class.respond_to?(:wrap)
|
160
|
+
|
161
|
+
->(input) { entity_class.new(input.to_h) }
|
51
162
|
end
|
52
163
|
|
53
|
-
|
54
|
-
|
164
|
+
# @abstract
|
165
|
+
#
|
166
|
+
# @example
|
167
|
+
# class Transaction::Repository < LunaPark::Repository
|
168
|
+
# # Parent of this mapper will be changed
|
169
|
+
# mapper do
|
170
|
+
# attr :foo
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# def self.base_anonymous_mapper
|
174
|
+
# MyBaseMapper
|
175
|
+
# end
|
176
|
+
# end
|
177
|
+
def base_anonymous_mapper
|
178
|
+
LunaPark::Mappers::Codirectional
|
179
|
+
end
|
180
|
+
|
181
|
+
def primary_key(attr)
|
182
|
+
@primary_key_attr = attr
|
55
183
|
end
|
56
184
|
|
57
185
|
DEFAULT_PRIMARY_KEY = :id
|
58
186
|
|
59
|
-
def
|
60
|
-
@
|
187
|
+
def primary_key_attr
|
188
|
+
@primary_key_attr || DEFAULT_PRIMARY_KEY
|
189
|
+
end
|
190
|
+
|
191
|
+
def __define_constants__(not_found: LunaPark::Extensions::DataMapper::NotFound)
|
192
|
+
__define_class__ 'NotFound', not_found
|
61
193
|
end
|
62
194
|
|
63
|
-
def
|
64
|
-
|
195
|
+
def __define_class__(name, parent)
|
196
|
+
klass = Class.new(parent)
|
197
|
+
const_set name, klass
|
65
198
|
end
|
199
|
+
|
200
|
+
def inherited(klass)
|
201
|
+
klass.__define_constants__(not_found: NotFound)
|
202
|
+
klass.entity entity_class, __entity_coercion__
|
203
|
+
klass.mapper mapper_class
|
204
|
+
super
|
205
|
+
end
|
206
|
+
|
207
|
+
class Undefined; end
|
208
|
+
private_constant :Undefined
|
66
209
|
end
|
67
210
|
|
68
211
|
module InstanceMethods
|
@@ -74,22 +217,56 @@ module LunaPark
|
|
74
217
|
|
75
218
|
# Helpers
|
76
219
|
|
220
|
+
# Repository Helpers
|
221
|
+
|
77
222
|
# Get collection of entities from row
|
78
223
|
# @example
|
79
224
|
# def where_type(type)
|
80
|
-
# read_all
|
225
|
+
# read_all scoped dataset.where(type: type)
|
81
226
|
# end
|
82
227
|
def read_all(rows)
|
83
|
-
to_entities from_rows rows
|
228
|
+
to_entities from_rows __to_array__(rows)
|
84
229
|
end
|
85
230
|
|
86
231
|
# Get one entity from row
|
87
232
|
# @example
|
88
233
|
# def find(id)
|
89
|
-
#
|
234
|
+
# # limit 2 allows to check if there are more than 1 record
|
235
|
+
# read_one dataset.where(id: id).limit(2)
|
236
|
+
# end
|
237
|
+
def read_one(rows)
|
238
|
+
to_entity from_row __one_from__(rows)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Get one entity from row
|
242
|
+
# @example
|
243
|
+
# def find!(id)
|
244
|
+
# read_one! dataset.where(id: id).limit(1)
|
245
|
+
# end
|
246
|
+
def read_one!(row, not_found_by: nil, not_found_meta: nil)
|
247
|
+
warn 'Deprecated option #not_found_meta used' unless not_found_meta.nil?
|
248
|
+
|
249
|
+
found! read_one(row), not_found_by: not_found_by || not_found_meta
|
250
|
+
end
|
251
|
+
|
252
|
+
# Check if record was found
|
253
|
+
# @example
|
254
|
+
# class MyRepository < LunaPark::Repository
|
255
|
+
# def find_by_x!(x)
|
256
|
+
# found! nil, not_found_by: "x: #{x}"
|
257
|
+
# end
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# begin
|
261
|
+
# MyRepository.new.find_by_x 'X'
|
262
|
+
# rescue MyRepository::NotFound => e
|
263
|
+
# raise HTTP404, "Record #{e.details[:name]} not found by #{e.details[:by]}"
|
90
264
|
# end
|
91
|
-
|
92
|
-
|
265
|
+
#
|
266
|
+
def found!(value, not_found_by: nil)
|
267
|
+
return value unless value.nil?
|
268
|
+
|
269
|
+
raise self.class::NotFound.new name: self.class.entity_class.name, by: not_found_by
|
93
270
|
end
|
94
271
|
|
95
272
|
# Mapper helpers
|
@@ -100,7 +277,7 @@ module LunaPark
|
|
100
277
|
# database.insert_many(rows)
|
101
278
|
# end
|
102
279
|
def to_rows(input_array)
|
103
|
-
mapper_class
|
280
|
+
self.class.mapper_class.to_rows(input_array)
|
104
281
|
end
|
105
282
|
|
106
283
|
# @example
|
@@ -109,7 +286,7 @@ module LunaPark
|
|
109
286
|
# database.insert(row)
|
110
287
|
# end
|
111
288
|
def to_row(input)
|
112
|
-
mapper_class
|
289
|
+
self.class.mapper_class.to_row(input)
|
113
290
|
end
|
114
291
|
|
115
292
|
# @example
|
@@ -118,7 +295,7 @@ module LunaPark
|
|
118
295
|
# entities_attrs.map { |entity_attrs| Entity.new(entity_attrs) }
|
119
296
|
# end
|
120
297
|
def from_rows(rows_array)
|
121
|
-
mapper_class
|
298
|
+
self.class.mapper_class.from_rows(rows_array)
|
122
299
|
end
|
123
300
|
|
124
301
|
# @example
|
@@ -130,7 +307,7 @@ module LunaPark
|
|
130
307
|
return if input.nil?
|
131
308
|
raise ArgumentError, 'Can not be an Array' if input.is_a?(Array)
|
132
309
|
|
133
|
-
mapper_class
|
310
|
+
self.class.mapper_class.from_row(input.to_h)
|
134
311
|
end
|
135
312
|
|
136
313
|
# Entity construction helpers
|
@@ -139,7 +316,7 @@ module LunaPark
|
|
139
316
|
# to_entities(attributes_hashes) # => Array of Entity
|
140
317
|
# to_entities(attributes_hash) # => Array of Entity
|
141
318
|
def to_entities(attrs_array)
|
142
|
-
|
319
|
+
__to_array__(attrs_array).map { |attrs| to_entity(attrs) }
|
143
320
|
end
|
144
321
|
|
145
322
|
# @example
|
@@ -147,48 +324,103 @@ module LunaPark
|
|
147
324
|
def to_entity(attrs)
|
148
325
|
return if attrs.nil?
|
149
326
|
|
150
|
-
entity_class
|
327
|
+
self.class.entity_class.new(attrs)
|
151
328
|
end
|
152
329
|
|
153
330
|
# Entity wrapping helpers
|
154
331
|
|
155
332
|
# @example
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
333
|
+
# wrap_all(attributes_hashes) # => Array of Entity
|
334
|
+
# wrap_all(entities) # => Array of Entity
|
335
|
+
# wrap_all(entity) # => Array of Entity
|
159
336
|
def wrap_all(input_array)
|
160
|
-
|
337
|
+
__to_array__(input_array).map { |input| wrap(input) }
|
161
338
|
end
|
162
339
|
|
163
340
|
# @example
|
164
|
-
#
|
165
|
-
#
|
341
|
+
# wrap(id: 42) # => <#MyEntity @id=42>
|
342
|
+
# wrap(entity) # => <#MyEntity @id=42>
|
166
343
|
def wrap(input)
|
167
344
|
return if input.nil?
|
168
345
|
|
169
|
-
|
346
|
+
self.class.__entity_coercion__.call(input)
|
170
347
|
end
|
171
348
|
|
172
|
-
#
|
173
|
-
|
174
|
-
|
175
|
-
|
349
|
+
# @example scope after query build
|
350
|
+
# def all(**opts)
|
351
|
+
# read_all scoped(**opts).order(:created_at)
|
352
|
+
# end
|
353
|
+
#
|
354
|
+
# @example scope before query build
|
355
|
+
# def all(**opts)
|
356
|
+
# read_all scoped(dataset.order(:created_at), **opts)
|
357
|
+
# end
|
358
|
+
#
|
359
|
+
def scoped(ds = dataset, **opts)
|
360
|
+
scope(ds, **opts)
|
176
361
|
end
|
177
362
|
|
178
|
-
|
179
|
-
|
363
|
+
# @abstract
|
364
|
+
#
|
365
|
+
# @example
|
366
|
+
# def scope(dataset, deleted: nil, for_update: false, **scope)
|
367
|
+
# ds = super(dataset, **scope)
|
368
|
+
# ds = ds.for_update if for_update == true
|
369
|
+
# ds = ds.where(deleted_at: nil) if deleted == false
|
370
|
+
# ds = ds.where.not(deleted_at: nil) if deleted == true
|
371
|
+
# ds
|
372
|
+
# end
|
373
|
+
#
|
374
|
+
# def all(**scope)
|
375
|
+
# read_all scoped(**scope) # same as `scope(dataset, **scope)`
|
376
|
+
# end
|
377
|
+
#
|
378
|
+
# all # get all
|
379
|
+
# all(deleted: false) # get not deleted
|
380
|
+
# all(deleted: true) # get deleted
|
381
|
+
def scope(dataset, **_scope)
|
382
|
+
dataset
|
180
383
|
end
|
181
384
|
|
385
|
+
# Read config
|
386
|
+
|
182
387
|
def primary_key
|
183
|
-
self.class.
|
388
|
+
self.class.primary_key_attr
|
184
389
|
end
|
185
390
|
|
186
391
|
# Factory Methods
|
187
392
|
|
393
|
+
# @abstract
|
394
|
+
#
|
188
395
|
# Usefull for extensions
|
189
396
|
def dataset
|
190
397
|
raise NotImplementedError
|
191
398
|
end
|
399
|
+
|
400
|
+
# fixes problem: `Array({ a: 1 }) # => [[:a, 1]]`
|
401
|
+
def __to_array__(input)
|
402
|
+
input.is_a?(Hash) ? [input] : Array(input)
|
403
|
+
end
|
404
|
+
|
405
|
+
# checks if there are only one item in the given array
|
406
|
+
def __one_from__(input)
|
407
|
+
case input
|
408
|
+
when Hash then input
|
409
|
+
else
|
410
|
+
array = input.is_a?(Array) ? input : Array(input)
|
411
|
+
raise MoreThanOneRecord.new count: array.size if array.size > 1
|
412
|
+
|
413
|
+
array.first
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
class NotFound < LunaPark::Errors::NotFound
|
419
|
+
message { |d| "#{d[:name]} (#{d[:by]})" }
|
420
|
+
end
|
421
|
+
|
422
|
+
class MoreThanOneRecord < LunaPark::Errors::System
|
423
|
+
message { |d| "Expected only one record, but there are #{d[:count]} records" }
|
192
424
|
end
|
193
425
|
end
|
194
426
|
end
|
@@ -7,9 +7,15 @@ module LunaPark
|
|
7
7
|
module Create
|
8
8
|
def create(input)
|
9
9
|
entity = wrap(input)
|
10
|
-
|
11
|
-
|
10
|
+
|
11
|
+
time = Time.now
|
12
|
+
entity.created_at = time if entity.respond_to?(:created_at)
|
13
|
+
entity.updated_at = time if entity.respond_to?(:updated_at)
|
14
|
+
|
15
|
+
row = to_row(entity)
|
16
|
+
new_row = dataset.returning.insert(row).first
|
12
17
|
new_attrs = from_row(new_row)
|
18
|
+
|
13
19
|
entity.set_attributes(new_attrs)
|
14
20
|
entity
|
15
21
|
end
|
@@ -5,56 +5,47 @@ module LunaPark
|
|
5
5
|
module Repositories
|
6
6
|
module Postgres
|
7
7
|
module Read
|
8
|
-
def find!(pk_value, for_update: false)
|
9
|
-
ds = dataset.where(primary_key => pk_value)
|
10
|
-
read_one!(ds, for_update: for_update, not_found_meta: pk_value)
|
11
|
-
end
|
12
|
-
|
13
|
-
def find(pk_value, for_update: false)
|
14
|
-
ds = dataset.where(primary_key => pk_value)
|
15
|
-
read_one(ds, for_update: for_update)
|
16
|
-
end
|
17
|
-
|
18
8
|
def lock!(pk_value)
|
19
|
-
|
9
|
+
transaction { yield find! pk_value, for_update: true }
|
20
10
|
end
|
21
11
|
|
22
12
|
def lock(pk_value)
|
23
|
-
|
13
|
+
transaction { yield find pk_value, for_update: true }
|
24
14
|
end
|
25
15
|
|
26
|
-
def
|
27
|
-
dataset.
|
16
|
+
def transaction(&block)
|
17
|
+
dataset.transaction(&block)
|
28
18
|
end
|
29
19
|
|
30
|
-
def
|
31
|
-
|
20
|
+
def find!(pk_value, **scope)
|
21
|
+
found! find(pk_value, **scope), not_found_by: pk_value
|
32
22
|
end
|
33
23
|
|
34
|
-
def
|
35
|
-
|
24
|
+
def find(pk_value, **scope)
|
25
|
+
read_one scoped(**scope).where(primary_key => pk_value)
|
36
26
|
end
|
37
27
|
|
38
|
-
|
28
|
+
def count(**scope)
|
29
|
+
scoped(**scope).count
|
30
|
+
end
|
39
31
|
|
40
|
-
def
|
41
|
-
|
42
|
-
raise Errors::NotFound, "#{short_class_name} (#{not_found_meta})" if entity.nil?
|
43
|
-
end
|
32
|
+
def all(**scope)
|
33
|
+
read_all(scoped(**scope).order { created_at.desc })
|
44
34
|
end
|
45
35
|
|
46
|
-
def
|
47
|
-
|
48
|
-
row = dataset.first
|
49
|
-
to_entity from_row(row)
|
36
|
+
def first(**scope)
|
37
|
+
read_one scoped(**scope).order(:created_at).first
|
50
38
|
end
|
51
39
|
|
52
|
-
def
|
53
|
-
|
40
|
+
def last(**scope)
|
41
|
+
read_one scoped(**scope).order(:created_at).last
|
54
42
|
end
|
55
43
|
|
56
|
-
|
57
|
-
|
44
|
+
private
|
45
|
+
|
46
|
+
def scope(dataset, for_update: false, **_)
|
47
|
+
dataset = dataset.for_update if for_update
|
48
|
+
dataset
|
58
49
|
end
|
59
50
|
end
|
60
51
|
end
|
@@ -7,10 +7,13 @@ module LunaPark
|
|
7
7
|
module Update
|
8
8
|
def save(input)
|
9
9
|
entity = wrap(input)
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
|
11
|
+
entity.updated_at = Time.now if entity.respond_to?(:updated_at)
|
12
|
+
|
13
|
+
row = to_row(entity)
|
14
|
+
new_row = dataset.where(primary_key => row[primary_key]).returning.update(row).first
|
13
15
|
new_attrs = from_row(new_row)
|
16
|
+
|
14
17
|
entity.set_attributes(new_attrs)
|
15
18
|
entity
|
16
19
|
end
|
@@ -7,32 +7,46 @@ module LunaPark
|
|
7
7
|
# Copyist for copiyng value between two schemas with DIFFERENT or NESTED paths
|
8
8
|
# (Works with only one described attribute)
|
9
9
|
class Nested
|
10
|
-
def initialize(attrs_path:, row_path:)
|
10
|
+
def initialize(attrs_path:, row_path:, mapper:, map_array:)
|
11
11
|
@attrs_path = attrs_path
|
12
12
|
@row_path = row_path
|
13
13
|
|
14
|
+
@mapper_config = mapper
|
15
|
+
@map_array = map_array
|
16
|
+
|
14
17
|
raise ArgumentError, 'attr path can not be nil' if attrs_path.nil?
|
15
18
|
raise ArgumentError, 'store path can not be nil' if row_path.nil?
|
19
|
+
raise ArgumentError, 'array option MUST be nil when no Mapper given' if map_array && mapper.nil?
|
16
20
|
end
|
17
21
|
|
18
22
|
def from_row(row:, attrs:)
|
19
|
-
copy_nested(from: row, to: attrs, from_path: @row_path, to_path: @attrs_path)
|
23
|
+
copy_nested(from: row, to: attrs, from_path: @row_path, to_path: @attrs_path, direction: :from_row)
|
20
24
|
end
|
21
25
|
|
22
26
|
def to_row(row:, attrs:)
|
23
|
-
copy_nested(from: attrs, to: row, from_path: @attrs_path, to_path: @row_path)
|
27
|
+
copy_nested(from: attrs, to: row, from_path: @attrs_path, to_path: @row_path, direction: :to_row)
|
24
28
|
end
|
25
29
|
|
26
30
|
private
|
27
31
|
|
28
|
-
def copy_nested(from:, to:, from_path:, to_path:)
|
32
|
+
def copy_nested(from:, to:, from_path:, to_path:, direction:)
|
29
33
|
value = read(from, from_path)
|
30
34
|
|
31
35
|
return if value == Undefined # omit undefined keys
|
32
36
|
|
37
|
+
value = apply_mapper(value, direction) unless mapper.nil?
|
38
|
+
|
33
39
|
write(to, to_path, value)
|
34
40
|
end
|
35
41
|
|
42
|
+
def apply_mapper(value, direction)
|
43
|
+
if @map_array
|
44
|
+
value.map { |v| mapper.public_send direction, v }
|
45
|
+
else
|
46
|
+
mapper.public_send direction, value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
36
50
|
def read(from, from_path)
|
37
51
|
if from_path.is_a?(Array) # when given `%i[key path]` - not just `:key`
|
38
52
|
read_nested(from, path: from_path)
|
@@ -82,6 +96,13 @@ module LunaPark
|
|
82
96
|
path.inject(nested_hash) { |output, key| output[key] ||= {} }
|
83
97
|
end
|
84
98
|
|
99
|
+
def mapper
|
100
|
+
return @mapper if instance_variable_defined?(:@mapper)
|
101
|
+
return @mapper = Object.const_get(@mapper_config) if @mapper_config.is_a? String
|
102
|
+
|
103
|
+
@mapper = @mapper_config
|
104
|
+
end
|
105
|
+
|
85
106
|
class Undefined; end
|
86
107
|
|
87
108
|
private_constant :Undefined
|
@@ -53,33 +53,28 @@ module LunaPark
|
|
53
53
|
# class Mappers::Transaction < LunaPark::Mappers::Codirectional
|
54
54
|
# attr :uid, row: :id
|
55
55
|
# attr %i[charge amount], row: :charge_amount
|
56
|
+
# attr :comment
|
56
57
|
# end
|
57
|
-
def attr(attr, row: nil)
|
58
|
-
return attrs(attr) if row.nil?
|
59
|
-
|
58
|
+
def attr(attr, row: attr, mapper: nil, array: nil)
|
60
59
|
attr_path = to_path(attr)
|
61
60
|
row_path = to_path(row)
|
62
61
|
|
63
|
-
if attr_path == row_path
|
64
|
-
|
62
|
+
if attr_path == row_path && !attr_path.is_a?(Array)
|
63
|
+
slice_copyist.add_key(attr_path)
|
65
64
|
else
|
66
|
-
nested_copyists << Copyists::Nested.new(
|
65
|
+
nested_copyists << Copyists::Nested.new(
|
66
|
+
attrs_path: attr_path, row_path: row_path,
|
67
|
+
mapper: mapper, map_array: array
|
68
|
+
)
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
70
72
|
# @example
|
71
73
|
# class Mappers::Transaction < LunaPark::Mappers::Codirectional
|
72
|
-
# attrs :
|
74
|
+
# attrs :created_at, :updated_at, :deleted_at
|
73
75
|
# end
|
74
76
|
def attrs(*common_keys)
|
75
|
-
common_keys.each
|
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
|
77
|
+
common_keys.each { |common_key| attr common_key }
|
83
78
|
end
|
84
79
|
|
85
80
|
def from_row(input)
|
@@ -6,7 +6,7 @@ module LunaPark
|
|
6
6
|
module Mappers
|
7
7
|
module Errors
|
8
8
|
class NotArray < LunaPark::Errors::System
|
9
|
-
message { |d| "input MUST
|
9
|
+
message { |d| "input MUST respond to #to_a, but given #{d[:input].class} `#{d[:input].inspect}`" }
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -67,28 +67,30 @@ module LunaPark
|
|
67
67
|
# Transforms array of rows to array of attribute hashes
|
68
68
|
def from_rows(rows)
|
69
69
|
return [] if rows.nil?
|
70
|
-
raise Errors::NotArray.new(input: rows)
|
70
|
+
raise Errors::NotArray.new(input: rows) if rows.is_a?(Hash)
|
71
|
+
raise Errors::NotArray.new(input: rows) unless rows.respond_to?(:to_a)
|
71
72
|
|
72
73
|
rows.to_a.map { |hash| from_row(hash) }
|
73
74
|
end
|
74
75
|
|
75
76
|
##
|
76
77
|
# Transforms array of attribute hashes to array of rows
|
77
|
-
def to_rows(
|
78
|
-
return [] if
|
79
|
-
raise Errors::NotArray.new(input:
|
78
|
+
def to_rows(attrs_array)
|
79
|
+
return [] if attrs_array.nil?
|
80
|
+
raise Errors::NotArray.new(input: rows) if attrs_array.is_a?(Hash)
|
81
|
+
raise Errors::NotArray.new(input: attrs_array) unless attrs_array.respond_to?(:to_a)
|
80
82
|
|
81
|
-
|
83
|
+
attrs_array.to_a.map { |entity| to_row(entity) }
|
82
84
|
end
|
83
85
|
|
84
86
|
# @abstract
|
85
|
-
def from_row(
|
86
|
-
|
87
|
+
def from_row(row)
|
88
|
+
row.to_h
|
87
89
|
end
|
88
90
|
|
89
91
|
# @abstract
|
90
|
-
def to_row(
|
91
|
-
|
92
|
+
def to_row(attrs)
|
93
|
+
attrs.to_h
|
92
94
|
end
|
93
95
|
end
|
94
96
|
end
|
@@ -48,17 +48,17 @@ module LunaPark
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
def error_payload(
|
51
|
+
def error_payload(err) # rubocop:disable Metrics/MethodLength
|
52
52
|
error_hash = {
|
53
|
-
class:
|
54
|
-
message:
|
53
|
+
class: err.class,
|
54
|
+
message: err.message
|
55
55
|
}
|
56
|
-
error_hash.merge!(backtrace: "\n" +
|
56
|
+
error_hash.merge!(backtrace: "\n" + err.backtrace.join("\n") + "\n") if err.backtrace
|
57
57
|
payload = {
|
58
58
|
error: error_hash,
|
59
59
|
ok: false
|
60
60
|
}
|
61
|
-
payload.merge!(details:
|
61
|
+
payload.merge!(details: err.details) if err.respond_to?(:details)
|
62
62
|
payload
|
63
63
|
end
|
64
64
|
|
data/lib/luna_park/version.rb
CHANGED
data/lib/luna_park.rb
CHANGED
@@ -66,8 +66,7 @@ LunaPark::Tools.if_gem_installed('dry-validation', '~> 1.1') { require 'luna_par
|
|
66
66
|
require 'luna_park/values/compound'
|
67
67
|
require 'luna_park/values/single'
|
68
68
|
require 'luna_park/values/attributable'
|
69
|
-
require 'luna_park/
|
70
|
-
require 'luna_park/mappers/codirectional'
|
69
|
+
require 'luna_park/mapper'
|
71
70
|
require 'luna_park/repository'
|
72
71
|
require 'luna_park/repositories/sequel'
|
73
72
|
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.
|
4
|
+
version: 0.13.0
|
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: 2023-
|
12
|
+
date: 2023-12-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bugsnag
|
@@ -424,6 +424,7 @@ files:
|
|
424
424
|
- lib/luna_park/http/request.rb
|
425
425
|
- lib/luna_park/http/response.rb
|
426
426
|
- lib/luna_park/http/send.rb
|
427
|
+
- lib/luna_park/mapper.rb
|
427
428
|
- lib/luna_park/mappers/codirectional.rb
|
428
429
|
- lib/luna_park/mappers/codirectional/copyists/nested.rb
|
429
430
|
- lib/luna_park/mappers/codirectional/copyists/slice.rb
|