pragma 2.1.1 → 2.2.0

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