pragma 1.2.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a231e9c37711edada72361aff42676d8713903b1
4
- data.tar.gz: a43091b07a58d402f346abe79abbc9fb88b086a5
3
+ metadata.gz: 27c896a2d1034183edbcae9079c7f435057e62da
4
+ data.tar.gz: bc5a709485d117f346b7d23880ab155e8d66392b
5
5
  SHA512:
6
- metadata.gz: 97a24156d5da33359875dfdd96f96d04e8c3243e727915036a9b72ddb4a964340ee5d648260a8718f63f75078e0da1849ed7a7c2100f6de9bc20a09897bbbd0c
7
- data.tar.gz: dab8c7acf8751282f8f8fa2a89c5e01afbe8e44a1bc45262fb7c3d8813e41b7080a8938f2311b792e9f643539f511e5066f6acad0705e2214c8d9824ee978eab
6
+ metadata.gz: b754320e5db9566992162c53ab8f1db93f5f073021745a5975ac7ea3ddd4c2334ebcaf39fb69447191c8dc2e2efd7574b685fa0703b222655303f2b00d4ebe79
7
+ data.tar.gz: 6067ff68d0fbaa81cc5de835109650bb4b7d4811a0795f0a76fc7b5dc6cbca8b042cbd54ebb9ea489dcec737e7b0df755d02b7609b2e3ab610e77312df537d66
@@ -24,26 +24,26 @@ Style/BlockDelimiters:
24
24
  Exclude:
25
25
  - 'spec/**/*'
26
26
 
27
- Style/AlignParameters:
27
+ Layout/AlignParameters:
28
28
  EnforcedStyle: with_fixed_indentation
29
29
 
30
- Style/ClosingParenthesisIndentation:
30
+ Layout/ClosingParenthesisIndentation:
31
31
  Enabled: false
32
32
 
33
33
  Metrics/LineLength:
34
34
  Max: 100
35
35
  AllowURI: true
36
36
 
37
- Style/FirstParameterIndentation:
37
+ Layout/FirstParameterIndentation:
38
38
  Enabled: false
39
39
 
40
- Style/MultilineMethodCallIndentation:
40
+ Layout/MultilineMethodCallIndentation:
41
41
  EnforcedStyle: indented
42
42
 
43
- Style/IndentArray:
43
+ Layout/IndentArray:
44
44
  EnforcedStyle: consistent
45
45
 
46
- Style/IndentHash:
46
+ Layout/IndentHash:
47
47
  EnforcedStyle: consistent
48
48
 
49
49
  Style/SignalException:
@@ -53,7 +53,7 @@ Style/BracesAroundHashParameters:
53
53
  EnforcedStyle: context_dependent
54
54
 
55
55
  Lint/EndAlignment:
56
- AlignWith: variable
56
+ EnforcedStyleAlignWith: variable
57
57
  AutoCorrect: true
58
58
 
59
59
  Style/AndOr:
@@ -68,7 +68,7 @@ RSpec/NamedSubject:
68
68
  RSpec/ExampleLength:
69
69
  Enabled: false
70
70
 
71
- Style/MultilineMethodCallBraceLayout:
71
+ Layout/MultilineMethodCallBraceLayout:
72
72
  Enabled: false
73
73
 
74
74
  Metrics/MethodLength:
@@ -85,3 +85,6 @@ Metrics/CyclomaticComplexity:
85
85
 
86
86
  Metrics/ClassLength:
87
87
  Enabled: false
88
+
89
+ Metrics/BlockLength:
90
+ Enabled: false
data/Gemfile CHANGED
@@ -4,3 +4,8 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'pry'
7
+
8
+ gem 'pragma-policy', github: 'pragmarb/pragma-policy'
9
+ gem 'pragma-operation', github: 'pragmarb/pragma-operation'
10
+ gem 'pragma-contract', github: 'pragmarb/pragma-contract'
11
+ gem 'pragma-decorator', github: 'pragmarb/pragma-decorator'
data/README.md CHANGED
@@ -24,25 +24,25 @@ Looking for a Rails integration? Check out [pragma-rails](https://github.com/pra
24
24
  Pragma was created with a very specific goal in mind: to make the development of JSON APIs a matter
25
25
  of hours, not days. In other words, Pragma is for JSON APIs what Rails is for web applications.
26
26
 
27
- In order to achieve that goal, some ground rules were needed. Here they are.
27
+ Here are the ground rules:
28
28
 
29
29
  1. **Pragma is opinionated.** With Pragma, you don't get to make a lot of choices and that's
30
30
  _exactly_ why people are using it: they want to focus on the business logic of their API rather
31
31
  than the useless details. We understand this approach will not work in some cases and that's
32
32
  alright. If you need more personalization, only use a subset of Pragma (see item 2) or something
33
33
  else.
34
- 2. **Pragma is modular.** Pragma is built as a set of gems (currently 4), plus some standalone
34
+ 2. **Pragma is modular.** Pragma is built as a set of gems (currently 6), plus some standalone
35
35
  tools. You can pick one or more modules and use them in your application as you see fit. Even
36
36
  though they are completely independent from each other, they nicely integrate and work best when
37
37
  used together, creating an ecosystem that will dramatically speed up your design and development
38
38
  process.
39
- 3. **Pragma is not designed to be Rails-free.** This does not mean that Pragma _is not_ Rails free.
40
- Our Rails integration is decoupled from the rest of the ecosystem and all of the gems, in their
41
- current state, _can_ be used without Rails. However, this is just a byproduct of the project's
42
- design: independence from Rails is not a goal of the Pragma ecosystem, so don't count on it too
43
- much.
39
+ 3. **Pragma is designed to be Rails-free.** Just as what happens with Trailblazer, our Rails
40
+ integration is decoupled from the rest of the ecosystem and all of the gems can be used without
41
+ Rails. This is just a byproduct of the project's design: Pragma is built with pure Ruby.
42
+ [pragma-rails](https://github.com/pragmarb/pragma-rails) is the only available framework
43
+ integration at the moment, but more will come!
44
44
 
45
- ## Why not Trailblazer?
45
+ ### Why not Trailblazer?
46
46
 
47
47
  [Trailblazer](https://github.com/trailblazer/trailblazer) and all of its companion projects are
48
48
  awesome. They are so awesome that Pragma is built on top of them: even though we're not using
@@ -50,7 +50,8 @@ the Trailblazer gem itself yet, many of the Pragma gems are simply extensions of
50
50
  counterparts:
51
51
 
52
52
  - decorators are [ROAR representers](https://github.com/apotonick/roar);
53
- - contracts are [Reform forms](https://github.com/apotonick/reform).
53
+ - contracts are [Reform forms](https://github.com/apotonick/reform);
54
+ - operations are [Trailblazer operations](https://github.com/trailblazer/trailblazer-operation).
54
55
 
55
56
  Trailblazer and Pragma have different (but similar) places in the Ruby world: Trailblazer is an
56
57
  architecture for building all kinds of web applications in an intelligent, rational way, while
@@ -82,7 +83,61 @@ $ gem install pragma
82
83
 
83
84
  ## Usage
84
85
 
85
- All documentation is in the [doc](https://github.com/pragmarb/pragma/tree/master/doc) folder.
86
+ ### Project Structure
87
+
88
+ This gem works best if you follow the recommended structure for organizing resources:
89
+
90
+ ```
91
+ └── api
92
+ └── v1
93
+ └── article
94
+ ├── contract
95
+ │   ├── create.rb
96
+ │   └── update.rb
97
+ ├── operation
98
+ │   ├── create.rb
99
+ │   ├── destroy.rb
100
+ │   ├── index.rb
101
+ │   └── update.rb
102
+ └── policy.rb
103
+ └── decorator.rb
104
+ ```
105
+
106
+ Your modules and classes would, of course, follow the same structure: `API::V1::Article::Policy` and
107
+ so on and so forth.
108
+
109
+ If you adhere to this structure, the gem will be able to locate all of your classes without any
110
+ explicit configuration. This will save you a lot of time and is highly recommended.
111
+
112
+ ### Fantastic Five
113
+
114
+ Pragma comes with five built-in operations, often referred to as Fantastic Five (or "FF" for
115
+ brevity). They are, of course, Index, Show, Create, Update and Destroy.
116
+
117
+ These operations leverage the full power of the integrated Pragma ecosystem and require all four
118
+ components to be properly installed and configured in your application. You may reconfigure them
119
+ to skip some of the steps, but it is highly recommended to use them as they come.
120
+
121
+ You can find these operations under [lib/pragma/operation](https://github.com/pragmarb/pragma/tree/master/lib/pragma/operation).
122
+ To use them, simply create your own operations and inherit from ours. For instance:
123
+
124
+ ```ruby
125
+ module API
126
+ module V1
127
+ module Article
128
+ module Operation
129
+ class Create < Pragma::Operation::Create
130
+ # This assumes that you have the following:
131
+ # - a policy that responds to #create?
132
+ # - a Create contract
133
+ # - a decorator
134
+ # - an Article model
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ ```
86
141
 
87
142
  ## Contributing
88
143
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'pragma/operation'
3
4
  require 'pragma/policy'
4
5
  require 'pragma/contract'
@@ -9,7 +10,17 @@ require 'will_paginate/array'
9
10
 
10
11
  require 'pragma/version'
11
12
 
12
- require 'pragma/operation/defaults'
13
+ require 'pragma/decorator/error'
14
+
15
+ require 'pragma/operation/macro/classes'
16
+ require 'pragma/operation/macro/decorator'
17
+ require 'pragma/operation/macro/pagination'
18
+ require 'pragma/operation/macro/policy'
19
+ require 'pragma/operation/macro/model'
20
+ require 'pragma/operation/macro/contract/build'
21
+ require 'pragma/operation/macro/contract/validate'
22
+ require 'pragma/operation/macro/contract/persist'
23
+
13
24
  require 'pragma/operation/index'
14
25
  require 'pragma/operation/show'
15
26
  require 'pragma/operation/create'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pragma
4
+ module Decorator
5
+ class Error < Pragma::Decorator::Base
6
+ property :error_type
7
+ property :error_message
8
+ property :meta
9
+ end
10
+ end
11
+ end
@@ -1,33 +1,24 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Pragma
3
4
  module Operation
4
- # Finds the requested record, authorizes it, updates it accordingly to the parameters and
5
- # responds with the decorated record.
5
+ # Creates a new record and responds with the decorated record.
6
6
  #
7
7
  # @author Alessandro Desantis
8
8
  class Create < Pragma::Operation::Base
9
- include Pragma::Operation::Defaults
10
-
11
- def call
12
- context.record = build_record
13
- context.contract = build_contract(context.record)
14
-
15
- validate! context.contract
16
- authorize! context.contract
17
-
18
- context.contract.save
19
- context.record.save!
20
-
21
- respond_with status: :created, resource: decorate(context.record)
22
- end
23
-
24
- protected
9
+ step Macro::Classes()
10
+ step Macro::Model()
11
+ step Macro::Policy(), fail_fast: true
12
+ step Macro::Contract::Build()
13
+ step Macro::Contract::Validate(), fail_fast: true
14
+ step Macro::Contract::Persist(), fail_fast: true
15
+ step Macro::Decorator()
16
+ step :respond!
25
17
 
26
- # Builds the requested record.
27
- #
28
- # @return [Object]
29
- def build_record
30
- self.class.model_klass.new
18
+ def respond!(options)
19
+ options['result.response'] = Response::Created.new(
20
+ entity: options['result.decorator.instance']
21
+ )
31
22
  end
32
23
  end
33
24
  end
@@ -1,28 +1,30 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Pragma
3
4
  module Operation
4
- # Finds the requested record, authorizes it and decorates it.
5
+ # Finds an existing record, destroys it and responds 204 No Content.
5
6
  #
6
7
  # @author Alessandro Desantis
7
8
  class Destroy < Pragma::Operation::Base
8
- include Pragma::Operation::Defaults
9
-
10
- def call
11
- context.record = find_record
12
- authorize! context.record
9
+ step Macro::Classes()
10
+ step Macro::Model(:find_by), fail_fast: true
11
+ step Macro::Policy(), fail_fast: true
12
+ step :destroy!
13
+ failure :handle_invalid_model!, fail_fast: true
14
+ step :respond!
13
15
 
14
- context.record.destroy!
15
-
16
- head :no_content
16
+ def destroy!(_options, model:, **)
17
+ model.destroy
17
18
  end
18
19
 
19
- protected
20
+ def handle_invalid_model!(options, model:, **)
21
+ options['result.response'] = Response::UnprocessableEntity.new(
22
+ errors: model.errors
23
+ ).decorate_with(Decorator::Error)
24
+ end
20
25
 
21
- # Finds the requested record.
22
- #
23
- # @return [Object]
24
- def find_record
25
- self.class.model_klass.find(params[:id])
26
+ def respond!(options)
27
+ options['result.response'] = Response::NoContent.new
26
28
  end
27
29
  end
28
30
  end
@@ -1,115 +1,33 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'trailblazer/dsl'
4
+
2
5
  module Pragma
3
6
  module Operation
4
- # Finds all records of the requested resource, authorizes them, paginates them and returns
5
- # the decorated collection.
7
+ # Finds all records of the requested resource, authorizes them, paginates them and decorates
8
+ # them.
6
9
  #
7
10
  # @author Alessandro Desantis
8
11
  class Index < Pragma::Operation::Base
9
- include Pragma::Operation::Defaults
10
-
11
- def call
12
- context.records = authorize_collection(find_records)
13
-
14
- begin
15
- context.records = context.records.paginate(page: page, per_page: per_page)
16
- rescue RangeError => e
17
- respond_with!(
18
- status: :bad_request,
19
- resource: {
20
- error_type: :invalid_page,
21
- error_message: e.message
22
- }
23
- )
24
- end
25
-
26
- respond_with(
27
- resource: decorate(context.records),
28
- status: :ok,
29
- headers: {
30
- 'Page' => context.records.current_page.to_i,
31
- 'Per-Page' => context.records.per_page,
32
- 'Total' => context.records.total_entries
33
- },
34
- links: {
35
- first: build_page_url(1),
36
- last: build_page_url(context.records.total_pages),
37
- next: (build_page_url(context.records.next_page) if context.records.next_page),
38
- prev: (build_page_url(context.records.previous_page) if context.records.previous_page)
39
- }
40
- )
41
- end
42
-
43
- protected
44
-
45
- # Finds all the records. By default, calls +.all+ on the model class, which is inferred from
46
- # the operation's namespace (e.g. +API::V1::Post::Operation::Index+ will retrieve all records
47
- # of the +Post+ model).
48
- #
49
- # @return [Enumerable]
50
- def find_records
51
- self.class.model_klass.all
52
- end
12
+ step Macro::Classes()
13
+ step :retrieve!
14
+ step :scope!
15
+ step Macro::Pagination(), fail_fast: true
16
+ step Macro::Decorator(name: :collection), fail_fast: true
17
+ step :respond!
53
18
 
54
- # Returns the name of the page parameter.
55
- #
56
- # @return [Symbol]
57
- def page_param
58
- :page
19
+ def retrieve!(options)
20
+ options['model'] = options['model.class'].all
59
21
  end
60
22
 
61
- # Returns the page number. By default, this is the page parameter or 1 if it is empty.
62
- #
63
- # @return [Fixnum]
64
- #
65
- # @see #page_param
66
- def page
67
- return 1 if !params[page_param] || params[page_param].empty?
68
- params[page_param].to_i
23
+ def scope!(options, current_user:, model:, **)
24
+ options['model'] = options['policy.default.scope.class'].new(current_user, model).resolve
69
25
  end
70
26
 
71
- # Returns the name of the per_page param.
72
- #
73
- # @return [Symbol]
74
- def per_page_param
75
- :per_page
76
- end
77
-
78
- # Returns the default number of records per page.
79
- #
80
- # @return [Fixnum]
81
- def default_per_page
82
- 30
83
- end
84
-
85
- # Returns the maximum number of records per page.
86
- #
87
- # @return [Fixnum]
88
- def max_per_page
89
- 100
90
- end
91
-
92
- # Returns the number of records to include per page. By default, this is the +per_page+
93
- # parameter, up to a maximum of {#max_per_page} records, or {#default_per_page} if the
94
- # parameter is not present.
95
- #
96
- # @return [Fixnum]
97
- #
98
- # @see #default_per_page
99
- # @see #max_per_page
100
- # @see #per_page_param
101
- def per_page
102
- return default_per_page if !params[per_page_param] || params[per_page_param].empty?
103
- params[per_page_param].to_i > max_per_page ? max_per_page : params[per_page_param].to_i
104
- end
105
-
106
- # Builds the URL to a specific page in the collection.
107
- #
108
- # @param page [Fixnum] a page number
109
- #
110
- # @return [String]
111
- def build_page_url(_page)
112
- nil
27
+ def respond!(options, model:, **)
28
+ options['result.response'] = Response::Ok.new(
29
+ entity: options['result.decorator.collection']
30
+ )
113
31
  end
114
32
  end
115
33
  end