pragma 2.4.0 → 2.5.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 +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.
|