pragma 0.1.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 +7 -0
- data/.gitignore +8 -0
- data/.rspec +3 -0
- data/.rubocop.yml +87 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +93 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/doc/01-sensible-defaults.md +50 -0
- data/doc/02-crud-operations.md +109 -0
- data/lib/pragma.rb +23 -0
- data/lib/pragma/operation/create.rb +33 -0
- data/lib/pragma/operation/defaults.rb +68 -0
- data/lib/pragma/operation/destroy.rb +29 -0
- data/lib/pragma/operation/index.rb +89 -0
- data/lib/pragma/operation/show.rb +27 -0
- data/lib/pragma/operation/update.rb +33 -0
- data/lib/pragma/version.rb +4 -0
- data/pragma.gemspec +35 -0
- metadata +220 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 791ad06332320a27e0d54dac93e730e141e7ccf4
|
4
|
+
data.tar.gz: b46f3e7cb4147dd5b4f1e87593b979ae30b67a4c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f9abcf4edfd280bfff4d932acfc40f4ff37856d1fa489abf4aba78ea04be9d95baca5109f6badcb615ac5b3ab916e66cc63c598c3c767d0c8e0fae0e5da03f91
|
7
|
+
data.tar.gz: 159c12e39c2f26d778b115d18543e9505eaef25eca6ddbc27cab4436e8fa74bc898be814281b7518f1b8255d58ca1a49fad28b73f22d285fb9e7dd180fd77224
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
TargetRubyVersion: 2.3
|
5
|
+
Include:
|
6
|
+
- '**/Gemfile'
|
7
|
+
- '**/Rakefile'
|
8
|
+
Exclude:
|
9
|
+
- 'bin/*'
|
10
|
+
- 'db/**/*'
|
11
|
+
- 'vendor/bundle/**/*'
|
12
|
+
- 'spec/spec_helper.rb'
|
13
|
+
- 'spec/rails_helper.rb'
|
14
|
+
- 'spec/support/**/*'
|
15
|
+
- 'config/**/*'
|
16
|
+
- '**/Rakefile'
|
17
|
+
- '**/Gemfile'
|
18
|
+
|
19
|
+
RSpec/DescribeClass:
|
20
|
+
Exclude:
|
21
|
+
- 'spec/requests/**/*'
|
22
|
+
|
23
|
+
Style/BlockDelimiters:
|
24
|
+
Exclude:
|
25
|
+
- 'spec/**/*'
|
26
|
+
|
27
|
+
Style/AlignParameters:
|
28
|
+
EnforcedStyle: with_fixed_indentation
|
29
|
+
|
30
|
+
Style/ClosingParenthesisIndentation:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Metrics/LineLength:
|
34
|
+
Max: 100
|
35
|
+
AllowURI: true
|
36
|
+
|
37
|
+
Style/FirstParameterIndentation:
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
Style/MultilineMethodCallIndentation:
|
41
|
+
EnforcedStyle: indented
|
42
|
+
|
43
|
+
Style/IndentArray:
|
44
|
+
EnforcedStyle: consistent
|
45
|
+
|
46
|
+
Style/IndentHash:
|
47
|
+
EnforcedStyle: consistent
|
48
|
+
|
49
|
+
Style/SignalException:
|
50
|
+
EnforcedStyle: semantic
|
51
|
+
|
52
|
+
Style/BracesAroundHashParameters:
|
53
|
+
EnforcedStyle: context_dependent
|
54
|
+
|
55
|
+
Lint/EndAlignment:
|
56
|
+
AlignWith: variable
|
57
|
+
AutoCorrect: true
|
58
|
+
|
59
|
+
Style/AndOr:
|
60
|
+
EnforcedStyle: conditionals
|
61
|
+
|
62
|
+
Style/MultilineBlockChain:
|
63
|
+
Enabled: false
|
64
|
+
|
65
|
+
RSpec/NamedSubject:
|
66
|
+
Enabled: false
|
67
|
+
|
68
|
+
RSpec/ExampleLength:
|
69
|
+
Enabled: false
|
70
|
+
|
71
|
+
Style/MultilineMethodCallBraceLayout:
|
72
|
+
Enabled: false
|
73
|
+
|
74
|
+
Metrics/MethodLength:
|
75
|
+
Enabled: false
|
76
|
+
|
77
|
+
Metrics/AbcSize:
|
78
|
+
Enabled: false
|
79
|
+
|
80
|
+
Metrics/PerceivedComplexity:
|
81
|
+
Enabled: false
|
82
|
+
|
83
|
+
Metrics/CyclomaticComplexity:
|
84
|
+
Enabled: false
|
85
|
+
|
86
|
+
Metrics/ClassLength:
|
87
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Alessandro Desantis
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# Pragma
|
2
|
+
|
3
|
+
[](https://travis-ci.org/pragmarb/pragma)
|
4
|
+
[](https://gemnasium.com/github.com/pragmarb/pragma)
|
5
|
+
[](https://codeclimate.com/github/pragmarb/pragma)
|
6
|
+
[](https://coveralls.io/github/pragmarb/pragma)
|
7
|
+
|
8
|
+
Welcome to Pragma, a pragmatic (duh!), opinionated architecture for building JSON APIs with Ruby!
|
9
|
+
|
10
|
+
You can think of this as a meta-gem that pulls in the following pieces:
|
11
|
+
|
12
|
+
- [Pragma::Operation](https://github.com/pragmarb/pragma-operation);
|
13
|
+
- [Pragma::Policy](https://github.com/pragmarb/pragma-policy);
|
14
|
+
- [Pragma::Decorator](https://github.com/pragmarb/pragma-decorator);
|
15
|
+
- [Pragma::Contract](https://github.com/pragmarb/pragma-contract).
|
16
|
+
|
17
|
+
Additionally, it also provides default CRUD operations that leverage all of the Pragma components
|
18
|
+
and will make creating new resources in your API a breeze.
|
19
|
+
|
20
|
+
Looking for a Rails integration? Check out [pragma-rails](https://github.com/pragmarb/pragma-rails)!
|
21
|
+
|
22
|
+
## Philosophy
|
23
|
+
|
24
|
+
Pragma was created with a very specific goal in mind: to make the development of JSON APIs a matter
|
25
|
+
of hours, not days. In other words, Pragma is for JSON APIs what Rails is for web applications.
|
26
|
+
|
27
|
+
In order to achieve that goal, some ground rules were needed. Here they are.
|
28
|
+
|
29
|
+
1. **Pragma is opinionated.** With Pragma, you don't get to make a lot of choices and that's
|
30
|
+
_exactly_ why people are using it: they want to focus on the business logic of their API rather
|
31
|
+
than the useless details. We understand this approach will not work in some cases and that's
|
32
|
+
alright. If you need more personalization, only use a subset of Pragma (see item 2) or something
|
33
|
+
else.
|
34
|
+
2. **Pragma is modular.** Pragma is built as a set of gems (currently 4), plus some standalone
|
35
|
+
tools. You can pick one or more modules and use them in your application as you see fit. Even
|
36
|
+
though they are completely independent from each other, they nicely integrate and work best when
|
37
|
+
used together, creating an ecosystem that will dramatically speed up your design and development
|
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.
|
44
|
+
|
45
|
+
## Why not Trailblazer?
|
46
|
+
|
47
|
+
[Trailblazer](https://github.com/trailblazer/trailblazer) and all of its companion projects are
|
48
|
+
awesome. They are so awesome that Pragma is built on top of them: even though we're not using
|
49
|
+
the Trailblazer gem itself yet, many of the Pragma gems are simply extensions of their Trailblazer
|
50
|
+
counterparts:
|
51
|
+
|
52
|
+
- decorators are [ROAR representers](https://github.com/apotonick/roar);
|
53
|
+
- contracts are [Reform forms](https://github.com/apotonick/reform).
|
54
|
+
|
55
|
+
Trailblazer and Pragma have different (but similar) places in the Ruby world: Trailblazer is an
|
56
|
+
architecture for building all kinds of web applications in an intelligent, rational way, while
|
57
|
+
Pragma is an architecture for building JSON APIs. We have shamelessly taken all of the flexibility
|
58
|
+
and awesomeness from the Trailblazer project and restricted it to a narrow field of work, providing
|
59
|
+
tools, helpers and integrations that could never be part of Trailblazer due to their specificity.
|
60
|
+
|
61
|
+
Thank you, guys!
|
62
|
+
|
63
|
+
## Installation
|
64
|
+
|
65
|
+
Add this line to your application's Gemfile:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
gem 'pragma'
|
69
|
+
```
|
70
|
+
|
71
|
+
And then execute:
|
72
|
+
|
73
|
+
```console
|
74
|
+
$ bundle
|
75
|
+
```
|
76
|
+
|
77
|
+
Or install it yourself as:
|
78
|
+
|
79
|
+
```console
|
80
|
+
$ gem install pragma
|
81
|
+
```
|
82
|
+
|
83
|
+
## Usage
|
84
|
+
|
85
|
+
All documentation is in the [doc](https://github.com/pragmarb/pragma/tree/master/doc) folder.
|
86
|
+
|
87
|
+
## Contributing
|
88
|
+
|
89
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/pragmarb/pragma.
|
90
|
+
|
91
|
+
## License
|
92
|
+
|
93
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "pragma"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Sensible defaults
|
2
|
+
|
3
|
+
This gem works best if you follow the recommended structure (partially borrowed from
|
4
|
+
[Trailblazer](https://github.com/trailblazer/trailblazer)) for organizing resources:
|
5
|
+
|
6
|
+
```
|
7
|
+
└── api
|
8
|
+
└── v1
|
9
|
+
└── post
|
10
|
+
├── contract
|
11
|
+
│ ├── create.rb
|
12
|
+
│ └── update.rb
|
13
|
+
├── operation
|
14
|
+
│ ├── create.rb
|
15
|
+
│ ├── destroy.rb
|
16
|
+
│ ├── index.rb
|
17
|
+
│ └── update.rb
|
18
|
+
└── policy.rb
|
19
|
+
└── decorator.rb
|
20
|
+
```
|
21
|
+
|
22
|
+
Your modules and classes would, of course, follow the same structure: `API::V1::Post::Policy`,
|
23
|
+
`API::V1::Post::Operation::Create` and so on and so forth.
|
24
|
+
|
25
|
+
If you adhere to this structure, the gem will be able to locate all of your classes without explicit
|
26
|
+
configuration (i.e. no `#policy` or `#contract` calls etc.). This will save you a lot of time and is
|
27
|
+
highly recommended, especially when used in conjunction with the provided CRUD operations.
|
28
|
+
|
29
|
+
To leverage automatic discovery, include `Pragma::Operation::Defaults` in your operation:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
module API
|
33
|
+
module V1
|
34
|
+
module Post
|
35
|
+
module Operation
|
36
|
+
class Create < Pragma::Operation::Base
|
37
|
+
include Pragma::Operation::Defaults
|
38
|
+
|
39
|
+
def call
|
40
|
+
# You can use `decorate`, `validate` and `authorize` without having to explicitly
|
41
|
+
# specify the decorator, validator and policy classes.
|
42
|
+
#
|
43
|
+
# ...
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# CRUD operations
|
2
|
+
|
3
|
+
Pragma ships with a default set of CRUD operations which should work for most standard API
|
4
|
+
resources. You can use them as they are, modify them or simply roll your own.
|
5
|
+
|
6
|
+
## The Index operation
|
7
|
+
|
8
|
+
Pragma provides a default implementation of the Index operation. Here's how it works:
|
9
|
+
|
10
|
+
1. it finds all records of the model;
|
11
|
+
2. it wraps the query in the policy to only return viewable records;
|
12
|
+
3. it responds with 200 OK and a paginated list of decorated records.
|
13
|
+
|
14
|
+
To create an Index operation, inherit from `Pragma::Operation::Index`:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
module API
|
18
|
+
module V1
|
19
|
+
module Post
|
20
|
+
module Operation
|
21
|
+
class Index < Pragma::Operation::Index
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
To override the defaults of this operation, have a look at the [source code](https://github.com/pragmarb/pragma/blob/master/lib/pragma/operation/index.rb).
|
30
|
+
|
31
|
+
## The Create operation
|
32
|
+
|
33
|
+
Pragma provides a default implementation of the Create operation. Here's how it works:
|
34
|
+
|
35
|
+
1. it builds a new instance of the model;
|
36
|
+
2. it wraps the model in the Create contract;
|
37
|
+
3. it validates and authorizes the contract;
|
38
|
+
4. it saves the record;
|
39
|
+
5. it responds with 201 Created and the decorated record.
|
40
|
+
|
41
|
+
To create a Create operation (pun intended), inherit from `Pragma::Operation::Create`:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
module API
|
45
|
+
module V1
|
46
|
+
module Post
|
47
|
+
module Operation
|
48
|
+
class Create < Pragma::Operation::Create
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
To override the defaults of this operation, have a look at the [source code](https://github.com/pragmarb/pragma/blob/master/lib/pragma/operation/create.rb).
|
57
|
+
|
58
|
+
## The Update operation
|
59
|
+
|
60
|
+
Pragma provides a default implementation of the Update operation. Here's how it works:
|
61
|
+
|
62
|
+
1. it finds an instance of the model by ID;
|
63
|
+
2. it wraps the model in the Update contract;
|
64
|
+
3. it validates and authorizes the contract;
|
65
|
+
4. it saves the record;
|
66
|
+
5. it responds with 200 OK and the decorated record.
|
67
|
+
|
68
|
+
To create an Update operation, inherit from `Pragma::Operation::Update`:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
module API
|
72
|
+
module V1
|
73
|
+
module Post
|
74
|
+
module Operation
|
75
|
+
class Update < Pragma::Operation::Update
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
To override the defaults of this operation, have a look at the [source code](https://github.com/pragmarb/pragma/blob/master/lib/pragma/operation/update.rb).
|
84
|
+
|
85
|
+
## The Destroy operation
|
86
|
+
|
87
|
+
Pragma provides a default implementation of the Destroy operation. Here's how it works:
|
88
|
+
|
89
|
+
1. it finds an instance of the model by ID;
|
90
|
+
2. it authorizes the record;
|
91
|
+
3. it destroys the record;
|
92
|
+
4. it responds with 204 No Content.
|
93
|
+
|
94
|
+
To create a Destroy operation, inherit from `Pragma::Operation::Destroy`:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
module API
|
98
|
+
module V1
|
99
|
+
module Post
|
100
|
+
module Operation
|
101
|
+
class Destroy < Pragma::Operation::Destroy
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
To override the defaults of this operation, have a look at the [source code](https://github.com/pragmarb/pragma/blob/master/lib/pragma/operation/destroy.rb).
|
data/lib/pragma.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'pragma/operation'
|
3
|
+
require 'pragma/policy'
|
4
|
+
require 'pragma/contract'
|
5
|
+
require 'pragma/decorator'
|
6
|
+
|
7
|
+
require 'will_paginate'
|
8
|
+
require 'will_paginate/array'
|
9
|
+
|
10
|
+
require 'pragma/version'
|
11
|
+
|
12
|
+
require 'pragma/operation/defaults'
|
13
|
+
require 'pragma/operation/index'
|
14
|
+
require 'pragma/operation/show'
|
15
|
+
require 'pragma/operation/create'
|
16
|
+
require 'pragma/operation/update'
|
17
|
+
require 'pragma/operation/destroy'
|
18
|
+
|
19
|
+
# A pragmatic architecture for building JSON APIs.
|
20
|
+
#
|
21
|
+
# @author Alessandro Desantis
|
22
|
+
module Pragma
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Pragma
|
3
|
+
module Operation
|
4
|
+
# Finds the requested record, authorizes it, updates it accordingly to the parameters and
|
5
|
+
# responds with the decorated record.
|
6
|
+
#
|
7
|
+
# @author Alessandro Desantis
|
8
|
+
class Create < Pragma::Operation::Base
|
9
|
+
include Pragma::Operation::Defaults
|
10
|
+
|
11
|
+
def call
|
12
|
+
record = build_record
|
13
|
+
contract = build_contract(record)
|
14
|
+
|
15
|
+
validate! contract
|
16
|
+
authorize! contract
|
17
|
+
|
18
|
+
contract.save
|
19
|
+
|
20
|
+
respond_with resource: decorate(record)
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
# Builds the requested record.
|
26
|
+
#
|
27
|
+
# @return [Object]
|
28
|
+
def build_record
|
29
|
+
self.class.model_klass.new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Pragma
|
3
|
+
module Operation
|
4
|
+
# Provides support for inferring decorator, policy and contract names from the class name.
|
5
|
+
#
|
6
|
+
# @author Alessandro Desantis
|
7
|
+
module Defaults
|
8
|
+
def self.included(klass)
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods # :nodoc:
|
13
|
+
# Returns the decorator class for the current resource (if the inferred class exists).
|
14
|
+
#
|
15
|
+
# If the operation name is +API::V1::Post::Operation::Show+, returns
|
16
|
+
# +API::V1::Post::Decorator+.
|
17
|
+
def decorator_klass
|
18
|
+
super || (computed_decorator_klass if class_exists?(computed_decorator_klass))
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the policy class for the current resource (if the inferred class exists).
|
22
|
+
#
|
23
|
+
# If the operation name is +API::V1::Post::Operation::Show+, returns
|
24
|
+
# +API::V1::Post::Policy+.
|
25
|
+
def policy_klass
|
26
|
+
super || (computed_policy_klass if class_exists?(computed_policy_klass))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the contract class for the current resource (if the inferred class exists).
|
30
|
+
#
|
31
|
+
# If the operation name is +API::V1::Post::Operation::Create+, returns
|
32
|
+
# +API::V1::Post::Contract::Create+.
|
33
|
+
def contract_klass
|
34
|
+
super || (computed_contract_klass if class_exists?(computed_contract_klass))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the model class for the current resource (if the inferred class exists).
|
38
|
+
#
|
39
|
+
# If the operation name is +API::V1::Post::Operation::Create+, returns +::Post+.
|
40
|
+
def model_klass
|
41
|
+
Object.const_get("::#{name.split('::')[0..-3].last}")
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def computed_decorator_klass
|
47
|
+
(name.split('::')[0..-3] << 'Decorator').join('::')
|
48
|
+
end
|
49
|
+
|
50
|
+
def computed_policy_klass
|
51
|
+
(name.split('::')[0..-3] << 'Policy').join('::')
|
52
|
+
end
|
53
|
+
|
54
|
+
def computed_contract_klass
|
55
|
+
name_parts = name.split('::')
|
56
|
+
(name_parts[0..-3] << 'Decorator' << name_parts.last).join('::')
|
57
|
+
end
|
58
|
+
|
59
|
+
def class_exists?(klass)
|
60
|
+
Object.const_get(klass)
|
61
|
+
true
|
62
|
+
rescue NameError
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Pragma
|
3
|
+
module Operation
|
4
|
+
# Finds the requested record, authorizes it and decorates it.
|
5
|
+
#
|
6
|
+
# @author Alessandro Desantis
|
7
|
+
class Destroy < Pragma::Operation::Base
|
8
|
+
include Pragma::Operation::Defaults
|
9
|
+
|
10
|
+
def call
|
11
|
+
record = find_record
|
12
|
+
authorize! record
|
13
|
+
|
14
|
+
record.destroy!
|
15
|
+
|
16
|
+
head :no_content
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
# Finds the requested record.
|
22
|
+
#
|
23
|
+
# @return [Object]
|
24
|
+
def find_record
|
25
|
+
self.class.model_klass.find(params[:id])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Pragma
|
3
|
+
module Operation
|
4
|
+
# Finds all records of the requested resource, authorizes them, paginates them and returns
|
5
|
+
# the decorated collection.
|
6
|
+
#
|
7
|
+
# @author Alessandro Desantis
|
8
|
+
class Index < Pragma::Operation::Base
|
9
|
+
include Pragma::Operation::Defaults
|
10
|
+
|
11
|
+
def call
|
12
|
+
records = authorize_collection(find_records)
|
13
|
+
records = records.paginate(page: page, per_page: per_page)
|
14
|
+
|
15
|
+
respond_with(
|
16
|
+
resource: decorate(records),
|
17
|
+
headers: {
|
18
|
+
'Page' => records.current_page.to_i,
|
19
|
+
'Per-Page' => records.per_page,
|
20
|
+
'Total' => records.total_entries
|
21
|
+
},
|
22
|
+
links: {
|
23
|
+
first: build_page_url(1),
|
24
|
+
last: build_page_url(records.total_pages),
|
25
|
+
next: (build_page_url(records.next_page) if records.next_page),
|
26
|
+
prev: (build_page_url(records.previous_page) if records.previous_page)
|
27
|
+
}
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
# Finds all the records. By default, calls +.all+ on the model class, which is inferred from
|
34
|
+
# the operation's namespace (e.g. +API::V1::Post::Operation::Index+ will retrieve all records
|
35
|
+
# of the +Post+ model).
|
36
|
+
#
|
37
|
+
# @return [Enumerable]
|
38
|
+
def find_records
|
39
|
+
self.class.model_klass.all
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the current page number. By default, this is the +page+ parameter or 1 if the
|
43
|
+
# parameter is not present.
|
44
|
+
#
|
45
|
+
# @return [Fixnum]
|
46
|
+
def page
|
47
|
+
return 1 if !params[:page] || params[:page].empty?
|
48
|
+
params[:page].to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the number of records to include per page. By default, this is the +per_page+
|
52
|
+
# parameter, up to a maximum of {#max_per_page} records, or {#default_per_page} if the
|
53
|
+
# parameter is not present.
|
54
|
+
#
|
55
|
+
# @return [Fixnum]
|
56
|
+
#
|
57
|
+
# @see #default_per_page
|
58
|
+
# @see #max_per_page
|
59
|
+
def per_page
|
60
|
+
return default_per_page if !params[:per_page] || params[:per_page].empty?
|
61
|
+
params[:per_page].to_i > max_per_page ? max_per_page : params[:per_page].to_i
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the default number of records per page.
|
65
|
+
#
|
66
|
+
# @return [Fixnum]
|
67
|
+
def default_per_page
|
68
|
+
30
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the maximum number of records per page.
|
72
|
+
#
|
73
|
+
# @return [Fixnum]
|
74
|
+
def max_per_page
|
75
|
+
100
|
76
|
+
end
|
77
|
+
|
78
|
+
# Builds the URL to a specific page in the collection.
|
79
|
+
#
|
80
|
+
# @param page [Fixnum] a page number
|
81
|
+
#
|
82
|
+
# @return [String]
|
83
|
+
def build_page_url(page)
|
84
|
+
context.page_url_builder ||= ->(_page) { nil }
|
85
|
+
context.page_url_builder.call(page)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Pragma
|
3
|
+
module Operation
|
4
|
+
# Finds the requested record, authorizes it and decorates it.
|
5
|
+
#
|
6
|
+
# @author Alessandro Desantis
|
7
|
+
class Show < Pragma::Operation::Base
|
8
|
+
include Pragma::Operation::Defaults
|
9
|
+
|
10
|
+
def call
|
11
|
+
record = find_record
|
12
|
+
authorize! record
|
13
|
+
|
14
|
+
respond_with resource: decorate(record)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
# Finds the requested record.
|
20
|
+
#
|
21
|
+
# @return [Object]
|
22
|
+
def find_record
|
23
|
+
self.class.model_klass.find(params[:id])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Pragma
|
3
|
+
module Operation
|
4
|
+
# Finds the requested record, authorizes it, updates it accordingly to the parameters and
|
5
|
+
# responds with the decorated record.
|
6
|
+
#
|
7
|
+
# @author Alessandro Desantis
|
8
|
+
class Update < Pragma::Operation::Base
|
9
|
+
include Pragma::Operation::Defaults
|
10
|
+
|
11
|
+
def call
|
12
|
+
record = find_record
|
13
|
+
contract = build_contract(record)
|
14
|
+
|
15
|
+
validate! contract
|
16
|
+
authorize! contract
|
17
|
+
|
18
|
+
contract.save
|
19
|
+
|
20
|
+
respond_with resource: decorate(record)
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
# Finds the requested record.
|
26
|
+
#
|
27
|
+
# @return [Object]
|
28
|
+
def find_record
|
29
|
+
self.class.model_klass.find(params[:id])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/pragma.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pragma/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'pragma'
|
8
|
+
spec.version = Pragma::VERSION
|
9
|
+
spec.authors = ['Alessandro Desantis']
|
10
|
+
spec.email = ['desa.alessandro@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'A pragmatic architecture for building JSON APIs with Ruby.'
|
13
|
+
spec.homepage = 'https://github.com/pragmarb/pragma'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'pragma-operation', '~> 1.2.1'
|
24
|
+
spec.add_dependency 'pragma-policy', '~> 0.1.0'
|
25
|
+
spec.add_dependency 'pragma-contract', '~> 0.1.0'
|
26
|
+
spec.add_dependency 'pragma-decorator', '~> 0.1.0'
|
27
|
+
spec.add_dependency 'will_paginate', '~> 3.1.5'
|
28
|
+
|
29
|
+
spec.add_development_dependency 'bundler'
|
30
|
+
spec.add_development_dependency 'rake'
|
31
|
+
spec.add_development_dependency 'rspec'
|
32
|
+
spec.add_development_dependency 'rubocop'
|
33
|
+
spec.add_development_dependency 'rubocop-rspec'
|
34
|
+
spec.add_development_dependency 'coveralls'
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pragma
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alessandro Desantis
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pragma-operation
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.2.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.2.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pragma-policy
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pragma-contract
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pragma-decorator
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.1.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.1.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: will_paginate
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.1.5
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.1.5
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubocop-rspec
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: coveralls
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description:
|
168
|
+
email:
|
169
|
+
- desa.alessandro@gmail.com
|
170
|
+
executables: []
|
171
|
+
extensions: []
|
172
|
+
extra_rdoc_files: []
|
173
|
+
files:
|
174
|
+
- ".gitignore"
|
175
|
+
- ".rspec"
|
176
|
+
- ".rubocop.yml"
|
177
|
+
- ".travis.yml"
|
178
|
+
- CHANGELOG.md
|
179
|
+
- Gemfile
|
180
|
+
- LICENSE.txt
|
181
|
+
- README.md
|
182
|
+
- Rakefile
|
183
|
+
- bin/console
|
184
|
+
- bin/setup
|
185
|
+
- doc/01-sensible-defaults.md
|
186
|
+
- doc/02-crud-operations.md
|
187
|
+
- lib/pragma.rb
|
188
|
+
- lib/pragma/operation/create.rb
|
189
|
+
- lib/pragma/operation/defaults.rb
|
190
|
+
- lib/pragma/operation/destroy.rb
|
191
|
+
- lib/pragma/operation/index.rb
|
192
|
+
- lib/pragma/operation/show.rb
|
193
|
+
- lib/pragma/operation/update.rb
|
194
|
+
- lib/pragma/version.rb
|
195
|
+
- pragma.gemspec
|
196
|
+
homepage: https://github.com/pragmarb/pragma
|
197
|
+
licenses:
|
198
|
+
- MIT
|
199
|
+
metadata: {}
|
200
|
+
post_install_message:
|
201
|
+
rdoc_options: []
|
202
|
+
require_paths:
|
203
|
+
- lib
|
204
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
210
|
+
requirements:
|
211
|
+
- - ">="
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: '0'
|
214
|
+
requirements: []
|
215
|
+
rubyforge_project:
|
216
|
+
rubygems_version: 2.5.2
|
217
|
+
signing_key:
|
218
|
+
specification_version: 4
|
219
|
+
summary: A pragmatic architecture for building JSON APIs with Ruby.
|
220
|
+
test_files: []
|