terrain 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +1 -0
- data/README.md +92 -9
- data/Rakefile +5 -0
- data/lib/terrain/config.rb +9 -0
- data/lib/terrain/errors.rb +23 -11
- data/lib/terrain/page.rb +63 -0
- data/lib/terrain/resource.rb +32 -1
- data/lib/terrain.rb +13 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/terrain/errors_spec.rb +22 -2
- data/spec/terrain/page_spec.rb +101 -0
- data/spec/terrain/resource_spec.rb +100 -2
- data/terrain.gemspec +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35b20c85da62fa29911c7f66fd6c3152dd138d21
|
4
|
+
data.tar.gz: 16e76c904d30e761b3667f0ae0c135fad8b7529c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42432a6d37702385300c453a526620951a797acfaeaece1f17b81546fb9b041921fb1ae5f452bd37892d5bccb2d6ec71a0037a811c250896f9e28558705f9fe2
|
7
|
+
data.tar.gz: 89b74868b62563bec9d303e926632559ee79a4d65d5759020d650b6cb9d35ca70e085f05d4d186017f94d457d9dad9956797bede36615216277ba59b3f7f8251
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
# Terrain
|
2
2
|
|
3
|
-
|
3
|
+
[![Build Status](https://travis-ci.org/scttnlsn/terrain.png?branch=master)](https://travis-ci.org/scttnlsn/terrain)
|
4
4
|
|
5
|
-
|
6
|
-
* basic CRUD
|
7
|
-
* serialization via
|
8
|
-
* authorization via [Pundit](https://github.com/elabs/pundit)
|
5
|
+
Opinionated toolkit for building CRUD APIs with Rails
|
9
6
|
|
10
7
|
## Install
|
11
8
|
|
@@ -17,6 +14,18 @@ gem 'terrain'
|
|
17
14
|
|
18
15
|
## Usage
|
19
16
|
|
17
|
+
* [Error handling](#error-handling)
|
18
|
+
* [Resources](#resources)
|
19
|
+
* [Authorization](#authorization)
|
20
|
+
* [Serialization](#serialization)
|
21
|
+
* [Querying](#querying)
|
22
|
+
* [Filtering](#filtering)
|
23
|
+
* [Ordering](#ordering)
|
24
|
+
* [Pagination](#pagination)
|
25
|
+
* [Relationships](#relationships)
|
26
|
+
* [CRUD operations](#crud-operations)
|
27
|
+
* [Config](#config)
|
28
|
+
|
20
29
|
### Error handling
|
21
30
|
|
22
31
|
```ruby
|
@@ -39,7 +48,8 @@ JSON responses are of the form:
|
|
39
48
|
{
|
40
49
|
"error": {
|
41
50
|
"key": "type_of_error",
|
42
|
-
"message": "Localized error message"
|
51
|
+
"message": "Localized error message",
|
52
|
+
"details": "Optional details"
|
43
53
|
}
|
44
54
|
}
|
45
55
|
```
|
@@ -55,7 +65,7 @@ class ExampleController < ApplicationController
|
|
55
65
|
private
|
56
66
|
|
57
67
|
def my_error
|
58
|
-
error_response(:type_of_error, 500)
|
68
|
+
error_response(:type_of_error, 500, { some: :details })
|
59
69
|
end
|
60
70
|
end
|
61
71
|
```
|
@@ -84,11 +94,75 @@ via [ActiveModelSerializers](https://github.com/rails-api/active_model_serialize
|
|
84
94
|
|
85
95
|
#### Querying
|
86
96
|
|
87
|
-
|
97
|
+
Records of a given resource are queried by requesting the `index` action.
|
98
|
+
|
99
|
+
##### Filtering
|
100
|
+
|
101
|
+
Queries are scoped to the results returned from the `resource_scope` method. By default this returns all records, however, you can override it to further filter the results (i.e. based on query params, nested route params, etc.):
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
class ExampleController < ApplicationController
|
105
|
+
include Terrain::Resource
|
106
|
+
|
107
|
+
resource Example, permit: [:foo, :bar, :baz]
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def resource_scope
|
112
|
+
scope = super
|
113
|
+
scope = scope.where(foo: params[:foo]) if params[:foo].present?
|
114
|
+
scope
|
115
|
+
end
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
##### Ordering
|
120
|
+
|
121
|
+
You can pass an `order` param to reorder the response records. Specify a comma-separated list of fields and prefix the field with a `-` for descending order:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
# corresponds to Example.order('foo', 'bar desc')
|
125
|
+
get :index, order: 'foo,-bar'
|
126
|
+
```
|
127
|
+
|
128
|
+
##### Pagination
|
129
|
+
|
130
|
+
To request a range of records, specify the range in an HTTP header:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
# Request the first 10 records
|
134
|
+
get :index, {}, { 'Range' => '0-9' }
|
135
|
+
```
|
136
|
+
|
137
|
+
All responses include a `Content-Range` header that specifies the exact range returned as well as a total count of records. i.e.
|
138
|
+
|
139
|
+
```
|
140
|
+
Content-Range: 0-9/100
|
141
|
+
```
|
142
|
+
|
143
|
+
You can also pass open ended ranges such as `10-` (i.e. skip the first 10 records).
|
144
|
+
|
145
|
+
##### Relationships
|
146
|
+
|
147
|
+
No model relationships are serialized in the response by default. To specify the set of relationships to be embedded in the response, pass a comma-separated list of relationships in the `include` param.
|
148
|
+
|
149
|
+
As an example, suppose we're querying for posts which each have many tags and belong to an author. We could embed those relationships with the following `include` param:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
get :index, include: 'author,tags'
|
153
|
+
```
|
154
|
+
|
155
|
+
Suppose now that the author also has a profile relationship. We could include the author, author profile and tags by passing:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
get :index, include: 'author.profile,tags'
|
159
|
+
```
|
160
|
+
|
161
|
+
Included relationships are automatically preloaded via the ActiveRecord `includes` method. The `include` param is also supported in `show` actions.
|
88
162
|
|
89
163
|
#### CRUD operations
|
90
164
|
|
91
|
-
You may need an action to perform additional steps beyond simple persistence. There are
|
165
|
+
You may need an action to perform additional steps beyond simple persistence. There are methods for performing CRUD operations that can be overridden (shown below with their default implementation):
|
92
166
|
|
93
167
|
```ruby
|
94
168
|
class ExampleController < ApplicationController
|
@@ -112,3 +186,12 @@ class ExampleController < ApplicationController
|
|
112
186
|
end
|
113
187
|
end
|
114
188
|
```
|
189
|
+
|
190
|
+
### Config
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
Terrain.configure do |config|
|
194
|
+
# Maximum number of records returned
|
195
|
+
config.max_records = Float::INFINITY
|
196
|
+
end
|
197
|
+
```
|
data/Rakefile
ADDED
data/lib/terrain/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'terrain/page'
|
2
|
+
|
1
3
|
module Terrain
|
2
4
|
module Errors
|
3
5
|
extend ActiveSupport::Concern
|
@@ -9,8 +11,25 @@ module Terrain
|
|
9
11
|
rescue_from 'ActionController::RoutingError', with: :route_not_found
|
10
12
|
rescue_from 'ActiveRecord::RecordInvalid', with: :record_invalid
|
11
13
|
|
14
|
+
rescue_from Terrain::Page::RangeError, with: :range_error
|
15
|
+
|
12
16
|
private
|
13
17
|
|
18
|
+
def error_response(key = :server_error, status = 500, details = nil)
|
19
|
+
result = {
|
20
|
+
error: {
|
21
|
+
key: key,
|
22
|
+
message: I18n.t("terrain.errors.#{key}", request: request)
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
if details.present?
|
27
|
+
result[:error][:details] = details
|
28
|
+
end
|
29
|
+
|
30
|
+
render json: result, status: status
|
31
|
+
end
|
32
|
+
|
14
33
|
def association_not_found
|
15
34
|
error_response(:association_not_found, 400)
|
16
35
|
end
|
@@ -31,19 +50,12 @@ module Terrain
|
|
31
50
|
error_response(:route_not_found, 404)
|
32
51
|
end
|
33
52
|
|
34
|
-
def record_invalid
|
35
|
-
error_response(:record_invalid, 422)
|
53
|
+
def record_invalid(error)
|
54
|
+
error_response(:record_invalid, 422, error.record.errors.to_hash)
|
36
55
|
end
|
37
56
|
|
38
|
-
def
|
39
|
-
|
40
|
-
error: {
|
41
|
-
key: key,
|
42
|
-
message: I18n.t("terrain.errors.#{key}", request: request)
|
43
|
-
}
|
44
|
-
}
|
45
|
-
|
46
|
-
render json: result, status: status
|
57
|
+
def range_error
|
58
|
+
error_response(:range_error, 416)
|
47
59
|
end
|
48
60
|
end
|
49
61
|
end
|
data/lib/terrain/page.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Terrain
|
2
|
+
class Page
|
3
|
+
class RangeError < StandardError; end
|
4
|
+
|
5
|
+
RANGE_REGEX = /^(?<from>[0-9]*)-(?<to>[0-9]*)$/
|
6
|
+
|
7
|
+
attr_reader :scope, :range
|
8
|
+
|
9
|
+
def initialize(scope, range = nil)
|
10
|
+
@scope = scope
|
11
|
+
@range = range
|
12
|
+
end
|
13
|
+
|
14
|
+
def bounds
|
15
|
+
@bounds ||= begin
|
16
|
+
if range.present?
|
17
|
+
if match
|
18
|
+
raise RangeError if from > to
|
19
|
+
[from, to]
|
20
|
+
else
|
21
|
+
raise RangeError
|
22
|
+
end
|
23
|
+
else
|
24
|
+
[0, count - 1]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def count
|
30
|
+
@count ||= scope.count
|
31
|
+
end
|
32
|
+
|
33
|
+
def records
|
34
|
+
from, to = bounds
|
35
|
+
limit = [to - from + 1, Terrain.config.max_records].min
|
36
|
+
@records ||= scope.offset(from).limit(limit)
|
37
|
+
end
|
38
|
+
|
39
|
+
def content_range
|
40
|
+
if count > 0
|
41
|
+
from, to = bounds
|
42
|
+
to = [to, from + records.count - 1].min
|
43
|
+
"#{from}-#{to}/#{count}"
|
44
|
+
else
|
45
|
+
'*/0'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def match
|
52
|
+
range.match(RANGE_REGEX)
|
53
|
+
end
|
54
|
+
|
55
|
+
def from
|
56
|
+
match && match[:from].present? ? match[:from].to_i : 0
|
57
|
+
end
|
58
|
+
|
59
|
+
def to
|
60
|
+
match && match[:to].present? ? match[:to].to_i : (count - 1)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/terrain/resource.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'active_model_serializers'
|
2
2
|
require 'pundit'
|
3
3
|
|
4
|
+
require 'terrain/page'
|
5
|
+
|
4
6
|
module Terrain
|
5
7
|
module Resource
|
6
8
|
extend ActiveSupport::Concern
|
@@ -19,7 +21,13 @@ module Terrain
|
|
19
21
|
|
20
22
|
module Actions
|
21
23
|
def index
|
22
|
-
|
24
|
+
scope = order(resource_scope)
|
25
|
+
|
26
|
+
range = request.headers['Range']
|
27
|
+
page = Terrain::Page.new(scope, range)
|
28
|
+
|
29
|
+
headers['Content-Range'] = page.content_range
|
30
|
+
render json: page.records, include: (params[:include] || [])
|
23
31
|
end
|
24
32
|
|
25
33
|
def create
|
@@ -88,6 +96,29 @@ module Terrain
|
|
88
96
|
end
|
89
97
|
end
|
90
98
|
|
99
|
+
def order(scope)
|
100
|
+
if params[:order].present?
|
101
|
+
order = params[:order].gsub(/ /, '').split(',').map do |field|
|
102
|
+
direction = 'asc'
|
103
|
+
|
104
|
+
if field[0] == '-'
|
105
|
+
direction = 'desc'
|
106
|
+
field = field[1..-1]
|
107
|
+
end
|
108
|
+
|
109
|
+
"#{field} #{direction}"
|
110
|
+
end
|
111
|
+
|
112
|
+
scope = scope.order(order)
|
113
|
+
end
|
114
|
+
|
115
|
+
scope
|
116
|
+
end
|
117
|
+
|
118
|
+
def resource_scope
|
119
|
+
preloaded_resource.all
|
120
|
+
end
|
121
|
+
|
91
122
|
def load_record
|
92
123
|
preloaded_resource.find(params[:id])
|
93
124
|
end
|
data/lib/terrain.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -43,7 +43,7 @@ module Helpers
|
|
43
43
|
def serialize(value, options = {})
|
44
44
|
options[:include] ||= []
|
45
45
|
if value.respond_to?(:each)
|
46
|
-
|
46
|
+
value.map { |item| serialize(item, options) }
|
47
47
|
else
|
48
48
|
ActiveModelSerializers::SerializableResource.new(value, options).as_json.symbolize_keys
|
49
49
|
end
|
data/spec/terrain/errors_spec.rb
CHANGED
@@ -59,16 +59,36 @@ describe 'Terrain::Errors', type: :controller do
|
|
59
59
|
it { expect_json('error.key', 'route_not_found') }
|
60
60
|
end
|
61
61
|
|
62
|
-
context '
|
62
|
+
context 'range error' do
|
63
|
+
controller do
|
64
|
+
def index
|
65
|
+
raise Terrain::Page::RangeError
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it { expect(response.status).to eq 416 }
|
70
|
+
it { expect_json_types(error: :object) }
|
71
|
+
it { expect_json_types('error.message', :string) }
|
72
|
+
it { expect_json('error.key', 'range_error') }
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'record invalid' do
|
76
|
+
let(:record) { Example.new }
|
77
|
+
|
63
78
|
controller do
|
64
79
|
def index
|
65
|
-
|
80
|
+
record = Example.new
|
81
|
+
record.valid?
|
82
|
+
raise ActiveRecord::RecordInvalid, record
|
66
83
|
end
|
67
84
|
end
|
68
85
|
|
86
|
+
before { record.valid? }
|
87
|
+
|
69
88
|
it { expect(response.status).to eq 422 }
|
70
89
|
it { expect_json_types(error: :object) }
|
71
90
|
it { expect_json_types('error.message', :string) }
|
72
91
|
it { expect_json('error.key', 'record_invalid') }
|
92
|
+
it { expect_json('error.details', record.errors.to_hash) }
|
73
93
|
end
|
74
94
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Terrain::Page do
|
4
|
+
let!(:records) { create_list(:example, 10) }
|
5
|
+
let(:scope) { Example.all }
|
6
|
+
let(:empty_scope) { Example.where('1 = 0') }
|
7
|
+
let(:range) { '0-4' }
|
8
|
+
let(:page) { described_class.new(scope, range) }
|
9
|
+
|
10
|
+
describe '#bounds' do
|
11
|
+
subject { page.bounds }
|
12
|
+
|
13
|
+
it { is_expected.to eq [0, 4] }
|
14
|
+
|
15
|
+
context 'with empty range' do
|
16
|
+
let(:range) { '5-5' }
|
17
|
+
|
18
|
+
it { is_expected.to eq [5, 5] }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with no upper bound' do
|
22
|
+
let(:range) { '5-' }
|
23
|
+
|
24
|
+
it { is_expected.to eq [5, 9] }
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with no lower bound' do
|
28
|
+
let(:range) { '-5' }
|
29
|
+
|
30
|
+
it { is_expected.to eq [0, 5] }
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with invalid range' do
|
34
|
+
let(:range) { '3-2' }
|
35
|
+
|
36
|
+
it { expect { subject }.to raise_error described_class::RangeError }
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with invalid upper bound' do
|
40
|
+
let(:range) { '5-x' }
|
41
|
+
|
42
|
+
it { expect { subject }.to raise_error described_class::RangeError }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#count' do
|
47
|
+
subject { page.count }
|
48
|
+
|
49
|
+
it { is_expected.to eq 10 }
|
50
|
+
|
51
|
+
context 'with no records' do
|
52
|
+
let(:scope) { empty_scope }
|
53
|
+
|
54
|
+
it { is_expected.to eq 0 }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#records' do
|
59
|
+
subject { page.records }
|
60
|
+
|
61
|
+
it { is_expected.to eq records[0..4] }
|
62
|
+
|
63
|
+
context 'with fewer records than requested range' do
|
64
|
+
let(:range) { '5-14' }
|
65
|
+
|
66
|
+
it { is_expected.to eq records[5..9] }
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with max records configured' do
|
70
|
+
before { Terrain.config.max_records = 3 }
|
71
|
+
after { Terrain.config.max_records = nil }
|
72
|
+
|
73
|
+
it { is_expected.to eq records[0..2] }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#content_range' do
|
78
|
+
subject { page.content_range }
|
79
|
+
|
80
|
+
it { is_expected.to eq '0-4/10' }
|
81
|
+
|
82
|
+
context 'with fewer records than requested range' do
|
83
|
+
let(:range) { '5-14' }
|
84
|
+
|
85
|
+
it { is_expected.to eq '5-9/10' }
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with max records configured' do
|
89
|
+
before { Terrain.config.max_records = 3 }
|
90
|
+
after { Terrain.config.max_records = nil }
|
91
|
+
|
92
|
+
it { is_expected.to eq '0-2/10' }
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with no records' do
|
96
|
+
let(:scope) { empty_scope }
|
97
|
+
|
98
|
+
it { is_expected.to eq '*/0' }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -8,7 +8,7 @@ describe 'Terrain::Resource', type: :controller do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
describe '#index' do
|
11
|
-
let!(:
|
11
|
+
let!(:records) { create_list(:example, 10) }
|
12
12
|
|
13
13
|
it 'responds with 200 status' do
|
14
14
|
get :index
|
@@ -17,7 +17,105 @@ describe 'Terrain::Resource', type: :controller do
|
|
17
17
|
|
18
18
|
it 'responds with serialized records' do
|
19
19
|
get :index
|
20
|
-
expect(response.body).to eq serialize(
|
20
|
+
expect(response.body).to eq serialize(records).to_json
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'responds with Content-Range header' do
|
24
|
+
get :index
|
25
|
+
expect(response.headers['Content-Range']).to eq Terrain::Page.new(Example.all).content_range
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'filtered' do
|
29
|
+
let(:record) { records.first }
|
30
|
+
|
31
|
+
before do
|
32
|
+
record.foo = 'test'
|
33
|
+
record.save!
|
34
|
+
end
|
35
|
+
|
36
|
+
controller do
|
37
|
+
resource Example
|
38
|
+
|
39
|
+
def resource_scope
|
40
|
+
super.where(foo: params[:foo])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'responds with filtered records' do
|
45
|
+
get :index, foo: 'test'
|
46
|
+
expect(response.body).to eq serialize([record]).to_json
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'ordered' do
|
51
|
+
let(:params) { ActionController::Parameters.new(order: 'foo') }
|
52
|
+
|
53
|
+
it 'responds with ordered records' do
|
54
|
+
get :index, params
|
55
|
+
expect(response.body).to eq serialize(Example.order('foo asc')).to_json
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'descending' do
|
59
|
+
let(:params) { ActionController::Parameters.new(order: '-foo') }
|
60
|
+
|
61
|
+
it 'responds with ordered records' do
|
62
|
+
get :index, params
|
63
|
+
expect(response.body).to eq serialize(Example.order('foo desc')).to_json
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'multiple sort columns' do
|
68
|
+
let(:params) { ActionController::Parameters.new(order: ' - foo , bar ') }
|
69
|
+
|
70
|
+
it 'responds with ordered records' do
|
71
|
+
get :index, params
|
72
|
+
expect(response.body).to eq serialize(Example.order('foo desc', 'bar asc')).to_json
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'paged' do
|
78
|
+
before { request.headers['Range'] = '0-4' }
|
79
|
+
|
80
|
+
it 'responds with requested records' do
|
81
|
+
get :index
|
82
|
+
expect(response.body).to eq serialize(records[0..4]).to_json
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'responds with Content-Range header' do
|
86
|
+
get :index
|
87
|
+
expect(response.headers['Content-Range']).to eq Terrain::Page.new(Example.all, '0-4').content_range
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'with relations' do
|
92
|
+
before do
|
93
|
+
records.each do |record|
|
94
|
+
create_list(:widget, 3, example: record)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'does not include relations in serialized record' do
|
99
|
+
get :index
|
100
|
+
expect(response.body).to eq serialize(records, include: []).to_json
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'with valid include' do
|
104
|
+
let(:params) { ActionController::Parameters.new(include: 'widgets') }
|
105
|
+
|
106
|
+
it 'includes relations in serialized record' do
|
107
|
+
get :index, params
|
108
|
+
expect(response.body).to eq serialize(records, include: ['widgets']).to_json
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'with invalid include' do
|
113
|
+
let(:params) { ActionController::Parameters.new(include: 'widgets,wrong') }
|
114
|
+
|
115
|
+
it 'raises error' do
|
116
|
+
expect { get :index, params }.to raise_error ActiveRecord::AssociationNotFoundError
|
117
|
+
end
|
118
|
+
end
|
21
119
|
end
|
22
120
|
end
|
23
121
|
|
data/terrain.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: terrain
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Nelson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -60,10 +60,14 @@ extra_rdoc_files: []
|
|
60
60
|
files:
|
61
61
|
- ".gitignore"
|
62
62
|
- ".rspec"
|
63
|
+
- ".travis.yml"
|
63
64
|
- Gemfile
|
64
65
|
- README.md
|
66
|
+
- Rakefile
|
65
67
|
- lib/terrain.rb
|
68
|
+
- lib/terrain/config.rb
|
66
69
|
- lib/terrain/errors.rb
|
70
|
+
- lib/terrain/page.rb
|
67
71
|
- lib/terrain/resource.rb
|
68
72
|
- spec/spec_helper.rb
|
69
73
|
- spec/support/example.rb
|
@@ -73,6 +77,7 @@ files:
|
|
73
77
|
- spec/support/widget.rb
|
74
78
|
- spec/support/widget_factory.rb
|
75
79
|
- spec/terrain/errors_spec.rb
|
80
|
+
- spec/terrain/page_spec.rb
|
76
81
|
- spec/terrain/resource_spec.rb
|
77
82
|
- terrain.gemspec
|
78
83
|
homepage: https://github.com/scttnlsn/terrain
|
@@ -108,4 +113,5 @@ test_files:
|
|
108
113
|
- spec/support/widget.rb
|
109
114
|
- spec/support/widget_factory.rb
|
110
115
|
- spec/terrain/errors_spec.rb
|
116
|
+
- spec/terrain/page_spec.rb
|
111
117
|
- spec/terrain/resource_spec.rb
|