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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +23 -1
  4. data/README.md +22 -15
  5. data/lib/pragma.rb +21 -15
  6. data/lib/pragma/filter/base.rb +17 -0
  7. data/lib/pragma/filter/equals.rb +18 -0
  8. data/lib/pragma/filter/ilike.rb +18 -0
  9. data/lib/pragma/filter/like.rb +18 -0
  10. data/lib/pragma/filter/scope.rb +18 -0
  11. data/lib/pragma/filter/where.rb +18 -0
  12. data/lib/pragma/macro/classes.rb +98 -0
  13. data/lib/pragma/macro/contract/build.rb +25 -0
  14. data/lib/pragma/macro/contract/persist.rb +25 -0
  15. data/lib/pragma/macro/contract/validate.rb +25 -0
  16. data/lib/pragma/macro/decorator.rb +80 -0
  17. data/lib/pragma/macro/filtering.rb +45 -0
  18. data/lib/pragma/macro/model.rb +28 -0
  19. data/lib/pragma/macro/ordering.rb +81 -0
  20. data/lib/pragma/macro/pagination.rb +94 -0
  21. data/lib/pragma/macro/policy.rb +41 -0
  22. data/lib/pragma/operation/create.rb +1 -1
  23. data/lib/pragma/operation/destroy.rb +2 -2
  24. data/lib/pragma/operation/filter.rb +7 -0
  25. data/lib/pragma/operation/index.rb +3 -3
  26. data/lib/pragma/operation/macro.rb +7 -0
  27. data/lib/pragma/operation/show.rb +1 -1
  28. data/lib/pragma/operation/update.rb +1 -1
  29. data/lib/pragma/version.rb +1 -1
  30. metadata +21 -17
  31. data/lib/pragma/operation/filter/base.rb +0 -20
  32. data/lib/pragma/operation/filter/equals.rb +0 -13
  33. data/lib/pragma/operation/filter/ilike.rb +0 -13
  34. data/lib/pragma/operation/filter/like.rb +0 -13
  35. data/lib/pragma/operation/macro/classes.rb +0 -102
  36. data/lib/pragma/operation/macro/contract/build.rb +0 -27
  37. data/lib/pragma/operation/macro/contract/persist.rb +0 -27
  38. data/lib/pragma/operation/macro/contract/validate.rb +0 -27
  39. data/lib/pragma/operation/macro/decorator.rb +0 -82
  40. data/lib/pragma/operation/macro/filtering.rb +0 -47
  41. data/lib/pragma/operation/macro/model.rb +0 -30
  42. data/lib/pragma/operation/macro/ordering.rb +0 -84
  43. data/lib/pragma/operation/macro/pagination.rb +0 -96
  44. data/lib/pragma/operation/macro/policy.rb +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d0c11764865411e45f53f08871eac04c2c56588e
4
- data.tar.gz: c38dd3967e1d5a387bddba14caef46579e3d31fb
2
+ SHA256:
3
+ metadata.gz: 33831eebb49b3b8bbe97ad98221f1a2c17c0bca329c60f4d0eea66c260a5b82a
4
+ data.tar.gz: e726495b25a7f62cdbdf6e15025963a358ff6e1732ddf45cc7b0a79d34d7f613
5
5
  SHA512:
6
- metadata.gz: 18043a03e95027f0dbe80a1bbf5a9bfbabdb56bc0f47a8bdf29f5500b8f64081fb8bf845c4653e60483dd5611394e8a7b2bb4063d5946122784432910a46debd
7
- data.tar.gz: e12c6b35442c76060402278d739595ce10fde7063bd3d6478ff02ec1b1c7ad3f3a04d342b8b174c3deed2c16e455b1486a6240e88b8a54ff2edb9f893f4779f8
6
+ metadata.gz: f821889bd9e24a5aaea95039508655f3170a85317b6cb9ee34676fcec99c9e9482dd294667847351ae3768ceccd4f2ad0ce56b68e4ac525662d527e1aea170f6
7
+ data.tar.gz: f4166fb8b737b3aa26fed21a5b3717a026339b465b4a29d628ef162890a94e1c70a26c597a97f5a5af1e481992dc37c6a4cd3727e0ce20ceb4987e229c84ba88
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ .ruby-version
@@ -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.1.1...HEAD
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::Operation::Macro::Classes()
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::Operation::Macro::Model(:build)
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::Operation::Macro::Model(:find_by), fail_fast: true
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::Operation::Macro::Policy(), fail_fast: true
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::Operation::Macro::Filtering()
313
+ step Pragma::Macro::Filtering()
312
314
  step :respond!
313
315
 
314
316
  self['filtering.filters'] = [
315
- Pragma::Operation::Filter::Equals.new(param: :by_category, column: :category_id),
316
- Pragma::Operation::Filter::Ilike.new(param: :by_title, column: :title)
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. The
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
- - `Equals`: performs an equality (`=`) comparison.
333
- - `Like`: performs a `LIKE` comparison.
334
- - `Ilike`: performs an `ILIKE` comparison.
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::Operation::Macro::Ordering(), fail_fast: true
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::Operation::Macro::Pagination(), fail_fast: true
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::Operation::Macro::Decorator(), fail_fast: true
478
+ step Pragma::Macro::Decorator(), fail_fast: true
472
479
  step :respond!
473
480
 
474
481
  def model!(params:, **)
@@ -12,21 +12,27 @@ require 'pragma/version'
12
12
 
13
13
  require 'pragma/decorator/error'
14
14
 
15
- require 'pragma/operation/filter/base'
16
- require 'pragma/operation/filter/equals'
17
- require 'pragma/operation/filter/like'
18
- require 'pragma/operation/filter/ilike'
19
-
20
- require 'pragma/operation/macro/classes'
21
- require 'pragma/operation/macro/decorator'
22
- require 'pragma/operation/macro/filtering'
23
- require 'pragma/operation/macro/ordering'
24
- require 'pragma/operation/macro/pagination'
25
- require 'pragma/operation/macro/policy'
26
- require 'pragma/operation/macro/model'
27
- require 'pragma/operation/macro/contract/build'
28
- require 'pragma/operation/macro/contract/validate'
29
- require 'pragma/operation/macro/contract/persist'
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,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Filter
5
+ class Base
6
+ attr_reader :param
7
+
8
+ def initialize(param:)
9
+ @param = param.to_sym
10
+ end
11
+
12
+ def apply(*)
13
+ fail NotImplementedError
14
+ end
15
+ end
16
+ end
17
+ end
@@ -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