pragma 2.1.1 → 2.2.0
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 +5 -5
- data/.gitignore +1 -0
- data/CHANGELOG.md +23 -1
- data/README.md +22 -15
- data/lib/pragma.rb +21 -15
- data/lib/pragma/filter/base.rb +17 -0
- data/lib/pragma/filter/equals.rb +18 -0
- data/lib/pragma/filter/ilike.rb +18 -0
- data/lib/pragma/filter/like.rb +18 -0
- data/lib/pragma/filter/scope.rb +18 -0
- data/lib/pragma/filter/where.rb +18 -0
- data/lib/pragma/macro/classes.rb +98 -0
- data/lib/pragma/macro/contract/build.rb +25 -0
- data/lib/pragma/macro/contract/persist.rb +25 -0
- data/lib/pragma/macro/contract/validate.rb +25 -0
- data/lib/pragma/macro/decorator.rb +80 -0
- data/lib/pragma/macro/filtering.rb +45 -0
- data/lib/pragma/macro/model.rb +28 -0
- data/lib/pragma/macro/ordering.rb +81 -0
- data/lib/pragma/macro/pagination.rb +94 -0
- data/lib/pragma/macro/policy.rb +41 -0
- data/lib/pragma/operation/create.rb +1 -1
- data/lib/pragma/operation/destroy.rb +2 -2
- data/lib/pragma/operation/filter.rb +7 -0
- data/lib/pragma/operation/index.rb +3 -3
- data/lib/pragma/operation/macro.rb +7 -0
- data/lib/pragma/operation/show.rb +1 -1
- data/lib/pragma/operation/update.rb +1 -1
- data/lib/pragma/version.rb +1 -1
- metadata +21 -17
- data/lib/pragma/operation/filter/base.rb +0 -20
- data/lib/pragma/operation/filter/equals.rb +0 -13
- data/lib/pragma/operation/filter/ilike.rb +0 -13
- data/lib/pragma/operation/filter/like.rb +0 -13
- data/lib/pragma/operation/macro/classes.rb +0 -102
- data/lib/pragma/operation/macro/contract/build.rb +0 -27
- data/lib/pragma/operation/macro/contract/persist.rb +0 -27
- data/lib/pragma/operation/macro/contract/validate.rb +0 -27
- data/lib/pragma/operation/macro/decorator.rb +0 -82
- data/lib/pragma/operation/macro/filtering.rb +0 -47
- data/lib/pragma/operation/macro/model.rb +0 -30
- data/lib/pragma/operation/macro/ordering.rb +0 -84
- data/lib/pragma/operation/macro/pagination.rb +0 -96
- data/lib/pragma/operation/macro/policy.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 33831eebb49b3b8bbe97ad98221f1a2c17c0bca329c60f4d0eea66c260a5b82a
|
4
|
+
data.tar.gz: e726495b25a7f62cdbdf6e15025963a358ff6e1732ddf45cc7b0a79d34d7f613
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f821889bd9e24a5aaea95039508655f3170a85317b6cb9ee34676fcec99c9e9482dd294667847351ae3768ceccd4f2ad0ce56b68e4ac525662d527e1aea170f6
|
7
|
+
data.tar.gz: f4166fb8b737b3aa26fed21a5b3717a026339b465b4a29d628ef162890a94e1c70a26c597a97f5a5af1e481992dc37c6a4cd3727e0ce20ceb4987e229c84ba88
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [2.2.0]
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Added ability to order by an association column
|
15
|
+
- Implemented the `action` option for the `Policy` macro
|
16
|
+
- Implemented the `Where` filter
|
17
|
+
- Implemented the `Scope` filter
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
|
21
|
+
- Pipetrees have been normalized to use strings and no exclamation marks
|
22
|
+
- Move macros to `Pragma::Macro` namespace and provide BC-compatibility
|
23
|
+
- Default name of `Contract::Build` steps is now `contract.[name].build`
|
24
|
+
- Default name of `Contract::Persist` steps is now `contract.[name].[method]`
|
25
|
+
|
26
|
+
### Fixed
|
27
|
+
|
28
|
+
- Fixed handling of validation errors in `Contract::Validate`
|
29
|
+
- Fixed handling of validation errors in `Contract::Persist`
|
30
|
+
|
10
31
|
## [2.1.1]
|
11
32
|
|
12
33
|
### Fixed
|
@@ -29,7 +50,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
29
50
|
|
30
51
|
First Pragma 2 release.
|
31
52
|
|
32
|
-
[Unreleased]: https://github.com/pragmarb/pragma/compare/v2.
|
53
|
+
[Unreleased]: https://github.com/pragmarb/pragma/compare/v2.2.0...HEAD
|
54
|
+
[2.2.0]: https://github.com/pragmarb/pragma/compare/v2.1.1...v2.2.0
|
33
55
|
[2.1.1]: https://github.com/pragmarb/pragma/compare/v2.1.0...v2.1.1
|
34
56
|
[2.1.0]: https://github.com/pragmarb/pragma/compare/v2.0.0...v2.1.0
|
35
57
|
[2.0.0]: https://github.com/pragmarb/pragma/compare/v1.2.6...v2.0.0
|
data/README.md
CHANGED
@@ -167,7 +167,7 @@ module API
|
|
167
167
|
module Operation
|
168
168
|
class Create < Pragma::Operation::Base
|
169
169
|
# Let the macro figure out class names.
|
170
|
-
step Pragma::
|
170
|
+
step Pragma::Macro::Classes()
|
171
171
|
step :execute!
|
172
172
|
|
173
173
|
# But override the contract.
|
@@ -211,7 +211,7 @@ module API
|
|
211
211
|
# This step can be done by Classes if you want.
|
212
212
|
self['model.class'] = ::Article
|
213
213
|
|
214
|
-
step Pragma::
|
214
|
+
step Pragma::Macro::Model(:build)
|
215
215
|
step :save!
|
216
216
|
|
217
217
|
def save!(options)
|
@@ -239,7 +239,7 @@ module API
|
|
239
239
|
# This step can be done by Classes if you want.
|
240
240
|
self['model.class'] = ::Article
|
241
241
|
|
242
|
-
step Pragma::
|
242
|
+
step Pragma::Macro::Model(:find_by), fail_fast: true
|
243
243
|
step :respond!
|
244
244
|
|
245
245
|
def respond!(options)
|
@@ -276,7 +276,9 @@ module API
|
|
276
276
|
self['policy.default.class'] = Policy
|
277
277
|
|
278
278
|
step :model!
|
279
|
-
step Pragma::
|
279
|
+
step Pragma::Macro::Policy(), fail_fast: true
|
280
|
+
# You can also specify a custom method to call on the policy:
|
281
|
+
# step Pragma::Macro::Policy(action: :custom_method), fail_fast: true
|
280
282
|
step :respond!
|
281
283
|
|
282
284
|
def model!(params:, **)
|
@@ -308,12 +310,12 @@ module API
|
|
308
310
|
module Operation
|
309
311
|
class Index < Pragma::Operation::Base
|
310
312
|
step :model!
|
311
|
-
step Pragma::
|
313
|
+
step Pragma::Macro::Filtering()
|
312
314
|
step :respond!
|
313
315
|
|
314
316
|
self['filtering.filters'] = [
|
315
|
-
Pragma::
|
316
|
-
Pragma::
|
317
|
+
Pragma::Filter::Equals.new(param: :by_category, column: :category_id),
|
318
|
+
Pragma::Filter::Ilike.new(param: :by_title, column: :title)
|
317
319
|
]
|
318
320
|
|
319
321
|
def model!(params:, **)
|
@@ -326,12 +328,17 @@ module API
|
|
326
328
|
end
|
327
329
|
```
|
328
330
|
|
329
|
-
With the example above, you are exposing the `by_category` filter and the `by_title` filters.
|
330
|
-
following filters are available for ActiveRecord currently:
|
331
|
+
With the example above, you are exposing the `by_category` filter and the `by_title` filters.
|
331
332
|
|
332
|
-
|
333
|
-
|
334
|
-
- `
|
333
|
+
The following filters are available for ActiveRecord currently:
|
334
|
+
|
335
|
+
- `Equals`: performs an equality (`=`) comparison (requires `:column`)-
|
336
|
+
- `Like`: performs a `LIKE` comparison (requires `:column`).
|
337
|
+
- `Ilike`: performs an `ILIKE` comparison (requires `:column`).
|
338
|
+
- `Where`: adds a generic `WHERE` clause (requires `:condition` and passes the parameter's value as
|
339
|
+
`:value`).
|
340
|
+
- `Scope`: calls a method on the collection (requires `:scope` and passes the parameter's value as
|
341
|
+
the first argument).
|
335
342
|
|
336
343
|
Support for more clauses as well as more ORMs will come soon.
|
337
344
|
|
@@ -360,7 +367,7 @@ module API
|
|
360
367
|
step :model!
|
361
368
|
|
362
369
|
# This will override `model` with the ordered relation.
|
363
|
-
step Pragma::
|
370
|
+
step Pragma::Macro::Ordering(), fail_fast: true
|
364
371
|
|
365
372
|
step :respond!
|
366
373
|
|
@@ -412,7 +419,7 @@ module API
|
|
412
419
|
step :model!
|
413
420
|
|
414
421
|
# This will override `model` with the paginated relation.
|
415
|
-
step Pragma::
|
422
|
+
step Pragma::Macro::Pagination(), fail_fast: true
|
416
423
|
|
417
424
|
step :respond!
|
418
425
|
|
@@ -468,7 +475,7 @@ module API
|
|
468
475
|
self['decorator.instance.class'] = Decorator::Instance
|
469
476
|
|
470
477
|
step :model!
|
471
|
-
step Pragma::
|
478
|
+
step Pragma::Macro::Decorator(), fail_fast: true
|
472
479
|
step :respond!
|
473
480
|
|
474
481
|
def model!(params:, **)
|
data/lib/pragma.rb
CHANGED
@@ -12,21 +12,27 @@ require 'pragma/version'
|
|
12
12
|
|
13
13
|
require 'pragma/decorator/error'
|
14
14
|
|
15
|
-
require 'pragma/
|
16
|
-
require 'pragma/
|
17
|
-
require 'pragma/
|
18
|
-
require 'pragma/
|
19
|
-
|
20
|
-
require 'pragma/
|
21
|
-
|
22
|
-
require 'pragma/operation/
|
23
|
-
|
24
|
-
require 'pragma/
|
25
|
-
require 'pragma/
|
26
|
-
require 'pragma/
|
27
|
-
require 'pragma/
|
28
|
-
require 'pragma/
|
29
|
-
require 'pragma/
|
15
|
+
require 'pragma/filter/base'
|
16
|
+
require 'pragma/filter/equals'
|
17
|
+
require 'pragma/filter/like'
|
18
|
+
require 'pragma/filter/ilike'
|
19
|
+
require 'pragma/filter/where'
|
20
|
+
require 'pragma/filter/scope'
|
21
|
+
|
22
|
+
require 'pragma/operation/filter'
|
23
|
+
|
24
|
+
require 'pragma/macro/classes'
|
25
|
+
require 'pragma/macro/decorator'
|
26
|
+
require 'pragma/macro/filtering'
|
27
|
+
require 'pragma/macro/ordering'
|
28
|
+
require 'pragma/macro/pagination'
|
29
|
+
require 'pragma/macro/policy'
|
30
|
+
require 'pragma/macro/model'
|
31
|
+
require 'pragma/macro/contract/build'
|
32
|
+
require 'pragma/macro/contract/validate'
|
33
|
+
require 'pragma/macro/contract/persist'
|
34
|
+
|
35
|
+
require 'pragma/operation/macro'
|
30
36
|
|
31
37
|
require 'pragma/operation/index'
|
32
38
|
require 'pragma/operation/show'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Filter
|
5
|
+
class Equals < Base
|
6
|
+
attr_reader :column
|
7
|
+
|
8
|
+
def initialize(column:, **other)
|
9
|
+
super(**other)
|
10
|
+
@column = column
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(relation:, value:)
|
14
|
+
relation.where(column => value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Filter
|
5
|
+
class Ilike < Base
|
6
|
+
attr_reader :column
|
7
|
+
|
8
|
+
def initialize(column:, **other)
|
9
|
+
super(**other)
|
10
|
+
@column = column
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(relation:, value:)
|
14
|
+
relation.where("#{column} ILIKE ?", "%#{value}%")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Filter
|
5
|
+
class Like < Base
|
6
|
+
attr_reader :column
|
7
|
+
|
8
|
+
def initialize(column:, **other)
|
9
|
+
super(**other)
|
10
|
+
@column = column
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(relation:, value:)
|
14
|
+
relation.where("#{column} LIKE ?", "%#{value}%")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Filter
|
5
|
+
class Scope < Base
|
6
|
+
attr_reader :scope
|
7
|
+
|
8
|
+
def initialize(scope:, **other)
|
9
|
+
super(**other)
|
10
|
+
@scope = scope
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(relation:, value:)
|
14
|
+
relation.public_send(scope, value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Filter
|
5
|
+
class Where < Base
|
6
|
+
attr_reader :condition
|
7
|
+
|
8
|
+
def initialize(condition:, **other)
|
9
|
+
super(**other)
|
10
|
+
@condition = condition
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(relation:, value:)
|
14
|
+
relation.where(condition, value: value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Macro
|
5
|
+
def self.Classes
|
6
|
+
step = ->(input, options) { Classes.for(input, options) }
|
7
|
+
[step, name: 'classes']
|
8
|
+
end
|
9
|
+
|
10
|
+
module Classes
|
11
|
+
class << self
|
12
|
+
def for(input, options)
|
13
|
+
{
|
14
|
+
'model.class' => expected_model_class(input, options),
|
15
|
+
'policy.default.class' => expected_policy_class(input, options),
|
16
|
+
'policy.default.scope.class' => expected_policy_scope_class(input, options),
|
17
|
+
'decorator.instance.class' => expected_instance_decorator_class(input, options),
|
18
|
+
'decorator.collection.class' => expected_collection_decorator_class(input, options),
|
19
|
+
'contract.default.class' => expected_contract_class(input, options)
|
20
|
+
}.each_pair do |key, value|
|
21
|
+
next if options[key]
|
22
|
+
|
23
|
+
# FIXME: This entire block is required to trigger Rails autoloading. Ugh.
|
24
|
+
begin
|
25
|
+
Object.const_get(value)
|
26
|
+
rescue NameError => e
|
27
|
+
# We check the error message to avoid silently ignoring other NameErrors
|
28
|
+
# thrown while initializing the constant.
|
29
|
+
if e.message.start_with?('uninitialized constant')
|
30
|
+
# Required instead of a simple equality check because loading
|
31
|
+
# API::V1::Post::Contract::Index might throw "uninitialized constant
|
32
|
+
# API::V1::Post::Contract" if the resource has no contracts at all.
|
33
|
+
error_constant = e.message.split.last
|
34
|
+
raise e unless value.sub(/\A::/, '').start_with?(error_constant)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
options[key] = (Object.const_get(value) if Object.const_defined?(value))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def resource_namespace(input, _options)
|
45
|
+
input.class.name.split('::')[0..-3]
|
46
|
+
end
|
47
|
+
|
48
|
+
def root_namespace(input, options)
|
49
|
+
resource_namespace = resource_namespace(input, options)
|
50
|
+
return [] if resource_namespace.first == 'API'
|
51
|
+
resource_namespace[0..((resource_namespace.index('API') || 1) - 1)]
|
52
|
+
end
|
53
|
+
|
54
|
+
def expected_model_class(input, options)
|
55
|
+
[
|
56
|
+
root_namespace(input, options).join('::'),
|
57
|
+
resource_namespace(input, options).last
|
58
|
+
].join('::')
|
59
|
+
end
|
60
|
+
|
61
|
+
def expected_policy_class(input, options)
|
62
|
+
[
|
63
|
+
resource_namespace(input, options),
|
64
|
+
'Policy'
|
65
|
+
].join('::')
|
66
|
+
end
|
67
|
+
|
68
|
+
def expected_policy_scope_class(input, options)
|
69
|
+
"#{expected_policy_class(input, options)}::Scope"
|
70
|
+
end
|
71
|
+
|
72
|
+
def expected_instance_decorator_class(input, options)
|
73
|
+
[
|
74
|
+
resource_namespace(input, options),
|
75
|
+
'Decorator',
|
76
|
+
'Instance'
|
77
|
+
].join('::')
|
78
|
+
end
|
79
|
+
|
80
|
+
def expected_collection_decorator_class(input, options)
|
81
|
+
[
|
82
|
+
resource_namespace(input, options),
|
83
|
+
'Decorator',
|
84
|
+
'Collection'
|
85
|
+
].join('::')
|
86
|
+
end
|
87
|
+
|
88
|
+
def expected_contract_class(input, options)
|
89
|
+
[
|
90
|
+
resource_namespace(input, options),
|
91
|
+
'Contract',
|
92
|
+
input.class.name.split('::').last
|
93
|
+
].join('::')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'trailblazer/operation/contract'
|
4
|
+
|
5
|
+
module Pragma
|
6
|
+
module Macro
|
7
|
+
module Contract
|
8
|
+
def self.Build(name: 'default', constant: nil, builder: nil)
|
9
|
+
step = lambda do |input, options|
|
10
|
+
Trailblazer::Operation::Contract::Build.for(
|
11
|
+
input,
|
12
|
+
options,
|
13
|
+
name: name,
|
14
|
+
constant: constant,
|
15
|
+
builder: builder
|
16
|
+
).tap do |contract|
|
17
|
+
contract.current_user = options['current_user']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
[step, name: "contract.#{name}.build"]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'trailblazer/operation/persist'
|
4
|
+
|
5
|
+
module Pragma
|
6
|
+
module Macro
|
7
|
+
module Contract
|
8
|
+
def self.Persist(method: :save, name: 'default')
|
9
|
+
step = lambda do |input, options|
|
10
|
+
Trailblazer::Operation::Pipetree::Step.new(
|
11
|
+
Trailblazer::Operation::Contract::Persist(method: method, name: name).first
|
12
|
+
).call(input, options).tap do |result|
|
13
|
+
unless result
|
14
|
+
options['result.response'] = Pragma::Operation::Response::UnprocessableEntity.new(
|
15
|
+
errors: options["contract.#{name}"].model.errors.messages
|
16
|
+
).decorate_with(Pragma::Decorator::Error)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
[step, name: "contract.#{name}.#{method}"]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|