pragma 2.4.0 → 2.5.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 +4 -4
- data/.rubocop.yml +3 -3
- data/CHANGELOG.md +14 -1
- data/lib/pragma.rb +8 -0
- data/lib/pragma/association_includer.rb +8 -0
- data/lib/pragma/association_includer/active_record.rb +41 -0
- data/lib/pragma/association_includer/base.rb +23 -0
- data/lib/pragma/association_includer/poro.rb +17 -0
- data/lib/pragma/decorator/error.rb +6 -0
- data/lib/pragma/macro.rb +47 -0
- data/lib/pragma/macro/classes.rb +8 -4
- data/lib/pragma/macro/contract/build.rb +2 -0
- data/lib/pragma/macro/contract/persist.rb +2 -0
- data/lib/pragma/macro/contract/validate.rb +2 -0
- data/lib/pragma/macro/decorator.rb +4 -3
- data/lib/pragma/macro/model.rb +4 -2
- data/lib/pragma/macro/policy.rb +3 -1
- data/lib/pragma/operation/index.rb +7 -2
- data/lib/pragma/version.rb +1 -1
- data/pragma.gemspec +1 -0
- metadata +22 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd39da8a90334eb2afe3aeffae4bf1b83b1f2847f7a501065bcea8f0df15e9d0
|
4
|
+
data.tar.gz: e4bd66a3c01463012edc6502eb636473338eeaae434d1b1544bd7d5166e40f08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36d424c34488a97c82b80974e558e7cd317ca9c2b1f1eb8f5961ea1b07e3b8a9d33696e8f2aca48059315fb17b6bff3dd5ee5f3eea30f802328f00f5f76f7096
|
7
|
+
data.tar.gz: 7f9832ae44752caf106f08ea0acdf0a5e24b1aa0b10850987e5461f2c4dc83c027f4ece5e3c365dc10fe1743d0b105b5424141ca87c09b363acbd4b7b05934e2
|
data/.rubocop.yml
CHANGED
@@ -33,16 +33,16 @@ Metrics/LineLength:
|
|
33
33
|
Max: 100
|
34
34
|
AllowURI: true
|
35
35
|
|
36
|
-
Layout/
|
36
|
+
Layout/IndentFirstArgument:
|
37
37
|
Enabled: false
|
38
38
|
|
39
39
|
Layout/MultilineMethodCallIndentation:
|
40
40
|
EnforcedStyle: indented
|
41
41
|
|
42
|
-
Layout/
|
42
|
+
Layout/IndentFirstArrayElement:
|
43
43
|
EnforcedStyle: consistent
|
44
44
|
|
45
|
-
Layout/
|
45
|
+
Layout/IndentFirstHashElement:
|
46
46
|
EnforcedStyle: consistent
|
47
47
|
|
48
48
|
Style/SignalException:
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [2.5.0]
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Expose `type` property for API errors
|
15
|
+
- Expanded associations are now eager-loaded automatically
|
16
|
+
- Calling macros whose support classes are undefined will now raise a `MissingSkillError`
|
17
|
+
|
18
|
+
### Fixed
|
19
|
+
|
20
|
+
- Fixed computation of classes for nested operations
|
21
|
+
|
10
22
|
## [2.4.0]
|
11
23
|
|
12
24
|
### Added
|
@@ -76,7 +88,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
76
88
|
|
77
89
|
First Pragma 2 release.
|
78
90
|
|
79
|
-
[Unreleased]: https://github.com/pragmarb/pragma/compare/v2.
|
91
|
+
[Unreleased]: https://github.com/pragmarb/pragma/compare/v2.5.0...HEAD
|
92
|
+
[2.5.0]: https://github.com/pragmarb/pragma/compare/v2.4.0...v2.5.0
|
80
93
|
[2.4.0]: https://github.com/pragmarb/pragma/compare/v2.3.0...v2.4.0
|
81
94
|
[2.3.0]: https://github.com/pragmarb/pragma/compare/v2.2.0...v2.3.0
|
82
95
|
[2.2.0]: https://github.com/pragmarb/pragma/compare/v2.1.1...v2.2.0
|
data/lib/pragma.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'adaptor'
|
4
|
+
|
3
5
|
require 'pragma/operation'
|
4
6
|
require 'pragma/policy'
|
5
7
|
require 'pragma/contract'
|
@@ -19,6 +21,7 @@ require 'pragma/filter/boolean'
|
|
19
21
|
|
20
22
|
require 'pragma/operation/filter'
|
21
23
|
|
24
|
+
require 'pragma/macro'
|
22
25
|
require 'pragma/macro/classes'
|
23
26
|
require 'pragma/macro/decorator'
|
24
27
|
require 'pragma/macro/filtering'
|
@@ -32,6 +35,11 @@ require 'pragma/macro/contract/persist'
|
|
32
35
|
|
33
36
|
require 'pragma/operation/macro'
|
34
37
|
|
38
|
+
require 'pragma/association_includer/base'
|
39
|
+
require 'pragma/association_includer/active_record'
|
40
|
+
require 'pragma/association_includer/poro'
|
41
|
+
require 'pragma/association_includer'
|
42
|
+
|
35
43
|
require 'pragma/operation/index'
|
36
44
|
require 'pragma/operation/show'
|
37
45
|
require 'pragma/operation/create'
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module AssociationIncluder
|
5
|
+
class ActiveRecord < Base
|
6
|
+
class << self
|
7
|
+
def supports?(relation)
|
8
|
+
defined?(::ActiveRecord::Relation) && relation.is_a?(::ActiveRecord::Relation)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def include_associations(expands)
|
13
|
+
relation.includes(validate_associations(
|
14
|
+
relation.model,
|
15
|
+
destruct_associations(expands)
|
16
|
+
))
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def destruct_associations(expands)
|
22
|
+
associations = {}
|
23
|
+
|
24
|
+
expands.each do |expand|
|
25
|
+
expand.split('.').inject(associations) do |accumulator, association|
|
26
|
+
accumulator[association] ||= {}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
associations
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_associations(model, associations)
|
34
|
+
Hash[associations.map do |(key, value)|
|
35
|
+
reflection = model.reflect_on_association(key.to_sym)
|
36
|
+
reflection ? [key, validate_associations(reflection.klass, value)] : [false, false]
|
37
|
+
end.select { |_, v| v }]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module AssociationIncluder
|
5
|
+
class Base
|
6
|
+
attr_reader :relation
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def supports?(_relation)
|
10
|
+
fail NotImplementedError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(relation)
|
15
|
+
@relation = relation
|
16
|
+
end
|
17
|
+
|
18
|
+
def include_associations(_expands)
|
19
|
+
fail NotImplementedError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/pragma/macro.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pragma
|
4
|
+
module Macro
|
5
|
+
class << self
|
6
|
+
# Returns a skill or raises a {#MissingSkillError}.
|
7
|
+
#
|
8
|
+
# @param macro [String] the name of the macro requiring the skill
|
9
|
+
# @param skill [String] the name of the string
|
10
|
+
# @param options [Hash] the +options+ hash of the operation
|
11
|
+
#
|
12
|
+
# @return [Object] the value of the skill
|
13
|
+
#
|
14
|
+
# @raise [MissingSkillError] if the skill is undefined or +nil+
|
15
|
+
#
|
16
|
+
# @private
|
17
|
+
def require_skill(macro, skill, options)
|
18
|
+
options[skill] || fail(MissingSkillError.new(macro, skill))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Error raised when a skill is required but not present.
|
23
|
+
#
|
24
|
+
# @private
|
25
|
+
class MissingSkillError < StandardError
|
26
|
+
# Initializes the error.
|
27
|
+
#
|
28
|
+
# @param macro [String] the macro requiring the skill
|
29
|
+
# @param skill [String] the name of the missing skill
|
30
|
+
def initialize(macro, skill)
|
31
|
+
message = <<~ERROR
|
32
|
+
You are attempting to use the #{macro} macro, but no `#{skill}' skill is defined.
|
33
|
+
|
34
|
+
You can define the skill by adding the following to your operation:
|
35
|
+
|
36
|
+
self['#{skill}'] = MyCustomClass
|
37
|
+
|
38
|
+
If the skill holds a class, this can happen when the required class (e.g. the contract
|
39
|
+
class) is not in the expected location. If that's the case, you can just move the class to
|
40
|
+
the expected location and avoid defining the skill manually.
|
41
|
+
ERROR
|
42
|
+
|
43
|
+
super message
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/pragma/macro/classes.rb
CHANGED
@@ -42,13 +42,15 @@ module Pragma
|
|
42
42
|
private
|
43
43
|
|
44
44
|
def resource_namespace(input, _options)
|
45
|
-
input.class.name.split('::')
|
45
|
+
parts = input.class.name.split('::')
|
46
|
+
parts[0..(parts.index('Operation') - 1)]
|
46
47
|
end
|
47
48
|
|
48
49
|
def root_namespace(input, options)
|
49
50
|
resource_namespace = resource_namespace(input, options)
|
50
|
-
return [] if
|
51
|
-
|
51
|
+
return [] if resource_namespace.first.casecmp('API').zero?
|
52
|
+
|
53
|
+
api_index = (resource_namespace.map(&:upcase).index('API') || 1)
|
52
54
|
resource_namespace[0..(api_index - 1)]
|
53
55
|
end
|
54
56
|
|
@@ -87,10 +89,12 @@ module Pragma
|
|
87
89
|
end
|
88
90
|
|
89
91
|
def expected_contract_class(input, options)
|
92
|
+
parts = input.class.name.split('::')
|
93
|
+
|
90
94
|
[
|
91
95
|
resource_namespace(input, options),
|
92
96
|
'Contract',
|
93
|
-
|
97
|
+
parts[(parts.index('Operation') + 1)..-1]
|
94
98
|
].join('::')
|
95
99
|
end
|
96
100
|
end
|
@@ -7,6 +7,8 @@ module Pragma
|
|
7
7
|
module Contract
|
8
8
|
def self.Build(name: 'default', constant: nil, builder: nil)
|
9
9
|
step = lambda do |input, options|
|
10
|
+
Macro.require_skill('Contract::Build', "contract.#{name}.class", options)
|
11
|
+
|
10
12
|
Trailblazer::Operation::Contract::Build.for(
|
11
13
|
input,
|
12
14
|
options,
|
@@ -7,6 +7,8 @@ module Pragma
|
|
7
7
|
module Contract
|
8
8
|
def self.Persist(method: :save, name: 'default')
|
9
9
|
step = lambda do |input, options|
|
10
|
+
Macro.require_skill('Contract::Persist', "contract.#{name}.class", options)
|
11
|
+
|
10
12
|
Trailblazer::Operation::Pipetree::Step.new(
|
11
13
|
Trailblazer::Operation::Contract::Persist(method: method, name: name).first
|
12
14
|
).call(input, options).tap do |result|
|
@@ -7,6 +7,8 @@ module Pragma
|
|
7
7
|
module Contract
|
8
8
|
def self.Validate(name: 'default', **args)
|
9
9
|
step = lambda do |input, options|
|
10
|
+
Macro.require_skill('Contract::Validate', "contract.#{name}.class", options)
|
11
|
+
|
10
12
|
Trailblazer::Operation::Pipetree::Step.new(
|
11
13
|
Trailblazer::Operation::Contract::Validate(**args).first
|
12
14
|
).call(input, options).tap do |result|
|
@@ -10,13 +10,13 @@ module Pragma
|
|
10
10
|
module Decorator
|
11
11
|
class << self
|
12
12
|
def for(_input, name, options)
|
13
|
+
klass = Macro.require_skill('Decorator', "decorator.#{name}.class", options)
|
14
|
+
|
13
15
|
set_defaults(options)
|
14
16
|
|
15
17
|
return false unless validate_params(options)
|
16
18
|
|
17
|
-
options["result.decorator.#{name}"] = options[
|
18
|
-
options['model']
|
19
|
-
)
|
19
|
+
options["result.decorator.#{name}"] = klass.new(options['model'])
|
20
20
|
|
21
21
|
validate_expansion(options, name)
|
22
22
|
end
|
@@ -63,6 +63,7 @@ module Pragma
|
|
63
63
|
|
64
64
|
def validate_expansion(options, name)
|
65
65
|
return true unless options["result.decorator.#{name}"].respond_to?(:validate_expansion)
|
66
|
+
|
66
67
|
options["result.decorator.#{name}"].validate_expansion(options['params'][:expand])
|
67
68
|
true
|
68
69
|
rescue Pragma::Decorator::Association::ExpansionError => e
|
data/lib/pragma/macro/model.rb
CHANGED
@@ -6,9 +6,11 @@ module Pragma
|
|
6
6
|
module Macro
|
7
7
|
def self.Model(action = nil)
|
8
8
|
step = lambda do |input, options|
|
9
|
+
klass = Macro.require_skill('Model', 'model.class', options)
|
10
|
+
|
9
11
|
Trailblazer::Operation::Pipetree::Step.new(
|
10
|
-
Trailblazer::Operation::Model.for(
|
11
|
-
'model.class' =>
|
12
|
+
Trailblazer::Operation::Model.for(klass, action),
|
13
|
+
'model.class' => klass,
|
12
14
|
'model.action' => action
|
13
15
|
).call(input, options).tap do |result|
|
14
16
|
unless result
|
data/lib/pragma/macro/policy.rb
CHANGED
@@ -10,7 +10,9 @@ module Pragma
|
|
10
10
|
module Policy
|
11
11
|
class << self
|
12
12
|
def for(input, name, options, action = nil)
|
13
|
-
|
13
|
+
klass = Macro.require_skill('Policy', "policy.#{name}.class", options)
|
14
|
+
|
15
|
+
policy = klass.new(
|
14
16
|
options['policy.context'] || options['current_user'],
|
15
17
|
options['model']
|
16
18
|
)
|
@@ -4,8 +4,6 @@ module Pragma
|
|
4
4
|
module Operation
|
5
5
|
# Finds all records of the requested resource, authorizes them, paginates them and decorates
|
6
6
|
# them.
|
7
|
-
#
|
8
|
-
# @author Alessandro Desantis
|
9
7
|
class Index < Pragma::Operation::Base
|
10
8
|
step Macro::Classes()
|
11
9
|
step :retrieve!, name: 'retrieve'
|
@@ -14,12 +12,19 @@ module Pragma
|
|
14
12
|
step Macro::Ordering()
|
15
13
|
step Macro::Pagination()
|
16
14
|
step Macro::Decorator(name: :collection)
|
15
|
+
step :include!, name: 'include'
|
17
16
|
step :respond!, name: 'respond'
|
18
17
|
|
19
18
|
def retrieve!(options)
|
20
19
|
options['model'] = options['model.class'].all
|
21
20
|
end
|
22
21
|
|
22
|
+
def include!(options)
|
23
|
+
options['model'] = AssociationIncluder
|
24
|
+
.load_adaptor(options['model'])
|
25
|
+
.include_associations(options['params'][:expand] || [])
|
26
|
+
end
|
27
|
+
|
23
28
|
# TODO: Turn this into a macro.
|
24
29
|
def scope!(options, model:, **)
|
25
30
|
options['model'] = options['policy.default.scope.class'].new(
|
data/lib/pragma/version.rb
CHANGED
data/pragma.gemspec
CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
22
|
spec.require_paths = ['lib']
|
23
23
|
|
24
|
+
spec.add_dependency 'adaptor', '~> 0.2'
|
24
25
|
spec.add_dependency 'pragma-contract', '~> 2.0'
|
25
26
|
spec.add_dependency 'pragma-decorator', '~> 2.0'
|
26
27
|
spec.add_dependency 'pragma-operation', '~> 2.0'
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pragma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro Desantis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: adaptor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.2'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: pragma-contract
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -197,6 +211,10 @@ files:
|
|
197
211
|
- bin/console
|
198
212
|
- bin/setup
|
199
213
|
- lib/pragma.rb
|
214
|
+
- lib/pragma/association_includer.rb
|
215
|
+
- lib/pragma/association_includer/active_record.rb
|
216
|
+
- lib/pragma/association_includer/base.rb
|
217
|
+
- lib/pragma/association_includer/poro.rb
|
200
218
|
- lib/pragma/decorator/error.rb
|
201
219
|
- lib/pragma/filter/base.rb
|
202
220
|
- lib/pragma/filter/boolean.rb
|
@@ -205,6 +223,7 @@ files:
|
|
205
223
|
- lib/pragma/filter/like.rb
|
206
224
|
- lib/pragma/filter/scope.rb
|
207
225
|
- lib/pragma/filter/where.rb
|
226
|
+
- lib/pragma/macro.rb
|
208
227
|
- lib/pragma/macro/classes.rb
|
209
228
|
- lib/pragma/macro/contract/build.rb
|
210
229
|
- lib/pragma/macro/contract/persist.rb
|
@@ -243,8 +262,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
243
262
|
- !ruby/object:Gem::Version
|
244
263
|
version: '0'
|
245
264
|
requirements: []
|
246
|
-
|
247
|
-
rubygems_version: 2.7.5
|
265
|
+
rubygems_version: 3.0.1
|
248
266
|
signing_key:
|
249
267
|
specification_version: 4
|
250
268
|
summary: A pragmatic architecture for building JSON APIs with Ruby.
|