pragma 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|