her 0.8.2 → 0.9.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 +4 -4
- data/.rspec +1 -1
- data/.rubocop.yml +1291 -0
- data/.travis.yml +2 -0
- data/README.md +23 -3
- data/her.gemspec +1 -3
- data/lib/her/middleware/json_api_parser.rb +1 -1
- data/lib/her/model/associations/association.rb +31 -0
- data/lib/her/model/associations/association_proxy.rb +1 -1
- data/lib/her/model/attributes.rb +2 -0
- data/lib/her/model/orm.rb +79 -6
- data/lib/her/model/parse.rb +8 -12
- data/lib/her/model/relation.rb +45 -1
- data/lib/her/version.rb +1 -1
- data/spec/api_spec.rb +34 -31
- data/spec/collection_spec.rb +25 -10
- data/spec/json_api/model_spec.rb +75 -72
- data/spec/middleware/accept_json_spec.rb +1 -1
- data/spec/middleware/first_level_parse_json_spec.rb +20 -20
- data/spec/middleware/json_api_parser_spec.rb +26 -7
- data/spec/middleware/second_level_parse_json_spec.rb +8 -9
- data/spec/model/associations/association_proxy_spec.rb +2 -5
- data/spec/model/associations_spec.rb +248 -161
- data/spec/model/attributes_spec.rb +106 -99
- data/spec/model/callbacks_spec.rb +58 -26
- data/spec/model/dirty_spec.rb +30 -29
- data/spec/model/http_spec.rb +67 -35
- data/spec/model/introspection_spec.rb +26 -22
- data/spec/model/nested_attributes_spec.rb +31 -31
- data/spec/model/orm_spec.rb +312 -155
- data/spec/model/parse_spec.rb +77 -77
- data/spec/model/paths_spec.rb +109 -109
- data/spec/model/relation_spec.rb +76 -68
- data/spec/model/validations_spec.rb +6 -6
- data/spec/model_spec.rb +17 -17
- data/spec/spec_helper.rb +2 -3
- data/spec/support/macros/model_macros.rb +2 -2
- metadata +32 -59
data/spec/json_api/model_spec.rb
CHANGED
@@ -1,163 +1,166 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe Her::JsonApi::Model do
|
4
4
|
before do
|
5
|
-
Her::API.setup :
|
5
|
+
Her::API.setup url: "https://api.example.com" do |connection|
|
6
6
|
connection.use Her::Middleware::JsonApiParser
|
7
7
|
connection.adapter :test do |stub|
|
8
|
-
stub.get("/users/1") do
|
9
|
-
[
|
8
|
+
stub.get("/users/1") do
|
9
|
+
[
|
10
10
|
200,
|
11
11
|
{},
|
12
12
|
{
|
13
13
|
data: {
|
14
14
|
id: 1,
|
15
|
-
type:
|
15
|
+
type: "users",
|
16
16
|
attributes: {
|
17
|
-
name: "Roger Federer"
|
18
|
-
}
|
17
|
+
name: "Roger Federer"
|
18
|
+
}
|
19
19
|
}
|
20
|
-
|
20
|
+
|
21
21
|
}.to_json
|
22
|
-
]
|
22
|
+
]
|
23
23
|
end
|
24
24
|
|
25
|
-
stub.get("/users") do
|
26
|
-
[
|
25
|
+
stub.get("/users") do
|
26
|
+
[
|
27
27
|
200,
|
28
28
|
{},
|
29
29
|
{
|
30
30
|
data: [
|
31
31
|
{
|
32
32
|
id: 1,
|
33
|
-
type:
|
33
|
+
type: "users",
|
34
34
|
attributes: {
|
35
|
-
name: "Roger Federer"
|
36
|
-
}
|
37
|
-
},
|
35
|
+
name: "Roger Federer"
|
36
|
+
}
|
37
|
+
},
|
38
38
|
{
|
39
39
|
id: 2,
|
40
|
-
type:
|
40
|
+
type: "users",
|
41
41
|
attributes: {
|
42
|
-
name: "Kei Nishikori"
|
43
|
-
}
|
42
|
+
name: "Kei Nishikori"
|
43
|
+
}
|
44
44
|
}
|
45
45
|
]
|
46
46
|
}.to_json
|
47
|
-
]
|
47
|
+
]
|
48
48
|
end
|
49
49
|
|
50
|
-
stub.post("/users", data:
|
51
|
-
|
50
|
+
stub.post("/users", data:
|
51
|
+
{
|
52
|
+
type: "users",
|
52
53
|
attributes: {
|
53
|
-
name: "Jeremy Lin"
|
54
|
-
}
|
55
|
-
}) do
|
56
|
-
[
|
54
|
+
name: "Jeremy Lin"
|
55
|
+
}
|
56
|
+
}) do
|
57
|
+
[
|
57
58
|
201,
|
58
59
|
{},
|
59
60
|
{
|
60
61
|
data: {
|
61
62
|
id: 3,
|
62
|
-
type:
|
63
|
+
type: "users",
|
63
64
|
attributes: {
|
64
|
-
name:
|
65
|
-
}
|
65
|
+
name: "Jeremy Lin"
|
66
|
+
}
|
66
67
|
}
|
67
|
-
|
68
|
+
|
68
69
|
}.to_json
|
69
|
-
]
|
70
|
+
]
|
70
71
|
end
|
71
72
|
|
72
|
-
stub.patch("/users/1", data:
|
73
|
-
|
73
|
+
stub.patch("/users/1", data:
|
74
|
+
{
|
75
|
+
type: "users",
|
74
76
|
id: 1,
|
75
77
|
attributes: {
|
76
|
-
name: "Fed GOAT"
|
77
|
-
}
|
78
|
-
}) do
|
79
|
-
[
|
78
|
+
name: "Fed GOAT"
|
79
|
+
}
|
80
|
+
}) do
|
81
|
+
[
|
80
82
|
200,
|
81
83
|
{},
|
82
84
|
{
|
83
85
|
data: {
|
84
86
|
id: 1,
|
85
|
-
type:
|
87
|
+
type: "users",
|
86
88
|
attributes: {
|
87
|
-
name:
|
88
|
-
}
|
89
|
+
name: "Fed GOAT"
|
90
|
+
}
|
89
91
|
}
|
90
|
-
|
92
|
+
|
91
93
|
}.to_json
|
92
|
-
]
|
94
|
+
]
|
93
95
|
end
|
94
96
|
|
95
|
-
stub.delete("/users/1")
|
96
|
-
[
|
97
|
-
|
97
|
+
stub.delete("/users/1") do
|
98
|
+
[204, {}, {}]
|
99
|
+
end
|
98
100
|
end
|
99
|
-
|
100
101
|
end
|
101
102
|
|
102
103
|
spawn_model("Foo::User", type: Her::JsonApi::Model)
|
103
104
|
end
|
104
105
|
|
105
|
-
it
|
106
|
+
it "allows configuration of type" do
|
106
107
|
spawn_model("Foo::Bar", type: Her::JsonApi::Model) do
|
107
108
|
type :foobars
|
108
109
|
end
|
109
110
|
|
110
|
-
expect(Foo::Bar.instance_variable_get(
|
111
|
+
expect(Foo::Bar.instance_variable_get("@type")).to eql("foobars")
|
111
112
|
end
|
112
113
|
|
113
|
-
it
|
114
|
+
it "finds models by id" do
|
114
115
|
user = Foo::User.find(1)
|
115
116
|
expect(user.attributes).to eql(
|
116
|
-
|
117
|
-
|
117
|
+
"id" => 1,
|
118
|
+
"name" => "Roger Federer"
|
118
119
|
)
|
119
120
|
end
|
120
121
|
|
121
|
-
it
|
122
|
+
it "finds a collection of models" do
|
122
123
|
users = Foo::User.all
|
123
|
-
expect(users.map(&:attributes)).to match_array(
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
124
|
+
expect(users.map(&:attributes)).to match_array(
|
125
|
+
[
|
126
|
+
{
|
127
|
+
"id" => 1,
|
128
|
+
"name" => "Roger Federer"
|
129
|
+
},
|
130
|
+
{
|
131
|
+
"id" => 2,
|
132
|
+
"name" => "Kei Nishikori"
|
133
|
+
}
|
134
|
+
]
|
135
|
+
)
|
133
136
|
end
|
134
137
|
|
135
|
-
it
|
136
|
-
user = Foo::User.new(name:
|
138
|
+
it "creates a Foo::User" do
|
139
|
+
user = Foo::User.new(name: "Jeremy Lin")
|
137
140
|
user.save
|
138
141
|
expect(user.attributes).to eql(
|
139
|
-
|
140
|
-
|
142
|
+
"id" => 3,
|
143
|
+
"name" => "Jeremy Lin"
|
141
144
|
)
|
142
145
|
end
|
143
146
|
|
144
|
-
it
|
147
|
+
it "updates a Foo::User" do
|
145
148
|
user = Foo::User.find(1)
|
146
|
-
user.name =
|
149
|
+
user.name = "Fed GOAT"
|
147
150
|
user.save
|
148
151
|
expect(user.attributes).to eql(
|
149
|
-
|
150
|
-
|
152
|
+
"id" => 1,
|
153
|
+
"name" => "Fed GOAT"
|
151
154
|
)
|
152
155
|
end
|
153
156
|
|
154
|
-
it
|
157
|
+
it "destroys a Foo::User" do
|
155
158
|
user = Foo::User.find(1)
|
156
159
|
expect(user.destroy).to be_destroyed
|
157
160
|
end
|
158
161
|
|
159
|
-
context
|
160
|
-
it
|
162
|
+
context "undefined methods" do
|
163
|
+
it "removes methods that are not compatible with json api" do
|
161
164
|
[:parse_root_in_json, :include_root_in_json, :root_element, :primary_key].each do |method|
|
162
165
|
expect { Foo::User.new.send(method, :foo) }.to raise_error NoMethodError, "Her::JsonApi::Model does not support the #{method} configuration option"
|
163
166
|
end
|
@@ -4,7 +4,7 @@ require "spec_helper"
|
|
4
4
|
describe Her::Middleware::AcceptJSON do
|
5
5
|
it "adds an Accept header" do
|
6
6
|
described_class.new.add_header({}).tap do |headers|
|
7
|
-
headers["Accept"].
|
7
|
+
expect(headers["Accept"]).to eq("application/json")
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
@@ -7,55 +7,55 @@ describe Her::Middleware::FirstLevelParseJSON do
|
|
7
7
|
let(:body_with_errors) { "{\"id\": 1, \"name\": \"Tobias Fünke\", \"errors\": { \"name\": [ \"not_valid\", \"should_be_present\" ] }, \"metadata\": 3}" }
|
8
8
|
let(:body_with_malformed_json) { "wut." }
|
9
9
|
let(:body_with_invalid_json) { "true" }
|
10
|
-
let(:empty_body) {
|
10
|
+
let(:empty_body) { "" }
|
11
11
|
let(:nil_body) { nil }
|
12
12
|
|
13
13
|
it "parses body as json" do
|
14
14
|
subject.parse(body_without_errors).tap do |json|
|
15
|
-
json[:data].
|
16
|
-
json[:metadata].
|
15
|
+
expect(json[:data]).to eq(id: 1, name: "Tobias Fünke")
|
16
|
+
expect(json[:metadata]).to eq(3)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
it "parses :body key as json in the env hash" do
|
21
|
-
env = { :
|
21
|
+
env = { body: body_without_errors }
|
22
22
|
subject.on_complete(env)
|
23
23
|
env[:body].tap do |json|
|
24
|
-
json[:data].
|
25
|
-
json[:metadata].
|
24
|
+
expect(json[:data]).to eq(id: 1, name: "Tobias Fünke")
|
25
|
+
expect(json[:metadata]).to eq(3)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
it
|
30
|
-
subject.parse(body_without_errors)[:errors].
|
29
|
+
it "ensures the errors are a hash if there are no errors" do
|
30
|
+
expect(subject.parse(body_without_errors)[:errors]).to eq({})
|
31
31
|
end
|
32
32
|
|
33
|
-
it
|
34
|
-
subject.parse(body_with_errors)[:errors].
|
33
|
+
it "ensures the errors are a hash if there are no errors" do
|
34
|
+
expect(subject.parse(body_with_errors)[:errors]).to eq(name: %w(not_valid should_be_present))
|
35
35
|
end
|
36
36
|
|
37
|
-
it
|
37
|
+
it "ensures that malformed JSON throws an exception" do
|
38
38
|
expect { subject.parse(body_with_malformed_json) }.to raise_error(Her::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "wut.")')
|
39
39
|
end
|
40
40
|
|
41
|
-
it
|
41
|
+
it "ensures that invalid JSON throws an exception" do
|
42
42
|
expect { subject.parse(body_with_invalid_json) }.to raise_error(Her::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "true")')
|
43
43
|
end
|
44
44
|
|
45
|
-
it
|
46
|
-
subject.parse(nil_body)[:data].
|
45
|
+
it "ensures that a nil response returns an empty hash" do
|
46
|
+
expect(subject.parse(nil_body)[:data]).to eq({})
|
47
47
|
end
|
48
48
|
|
49
|
-
it
|
50
|
-
subject.parse(empty_body)[:data].
|
49
|
+
it "ensures that an empty response returns an empty hash" do
|
50
|
+
expect(subject.parse(empty_body)[:data]).to eq({})
|
51
51
|
end
|
52
52
|
|
53
|
-
context
|
54
|
-
it
|
55
|
-
env = { :
|
53
|
+
context "with status code 204" do
|
54
|
+
it "returns an empty body" do
|
55
|
+
env = { status: 204 }
|
56
56
|
subject.on_complete(env)
|
57
57
|
env[:body].tap do |json|
|
58
|
-
json[:data].
|
58
|
+
expect(json[:data]).to eq({})
|
59
59
|
end
|
60
60
|
end
|
61
61
|
end
|
@@ -12,21 +12,40 @@ describe Her::Middleware::JsonApiParser do
|
|
12
12
|
subject.on_complete(env)
|
13
13
|
env.fetch(:body).tap do |json|
|
14
14
|
expect(json[:data]).to eql(
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
15
|
+
type: "foo",
|
16
|
+
id: "bar",
|
17
|
+
attributes: { baz: "qux" }
|
18
18
|
)
|
19
19
|
expect(json[:errors]).to eql([])
|
20
|
-
expect(json[:metadata]).to eql(:
|
20
|
+
expect(json[:metadata]).to eql(api: "json api")
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
context "with status code 204" do
|
26
|
+
it "returns an empty body" do
|
27
|
+
env = { status: 204 }
|
28
|
+
subject.on_complete(env)
|
29
|
+
env[:body].tap do |json|
|
30
|
+
expect(json[:data]).to eq({})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with status code 304' do
|
36
|
+
it 'returns an empty body' do
|
37
|
+
env = { :status => 304 }
|
38
|
+
subject.on_complete(env)
|
39
|
+
env[:body].tap do |json|
|
40
|
+
expect(json[:data]).to eq({})
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# context "with invalid JSON body" do
|
26
46
|
# let(:body) { '"foo"' }
|
27
47
|
# it 'ensures that invalid JSON throws an exception' do
|
28
48
|
# expect { subject.parse(body) }.to raise_error(Her::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "\"foo\"")')
|
29
49
|
# end
|
30
|
-
#end
|
31
|
-
|
50
|
+
# end
|
32
51
|
end
|
@@ -8,28 +8,27 @@ describe Her::Middleware::SecondLevelParseJSON do
|
|
8
8
|
let(:body) { "{\"data\": 1, \"errors\": 2, \"metadata\": 3}" }
|
9
9
|
it "parses body as json" do
|
10
10
|
subject.parse(body).tap do |json|
|
11
|
-
json[:data].
|
12
|
-
json[:errors].
|
13
|
-
json[:metadata].
|
11
|
+
expect(json[:data]).to eq(1)
|
12
|
+
expect(json[:errors]).to eq(2)
|
13
|
+
expect(json[:metadata]).to eq(3)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
17
|
it "parses :body key as json in the env hash" do
|
18
|
-
env = { :
|
18
|
+
env = { body: body }
|
19
19
|
subject.on_complete(env)
|
20
20
|
env[:body].tap do |json|
|
21
|
-
json[:data].
|
22
|
-
json[:errors].
|
23
|
-
json[:metadata].
|
21
|
+
expect(json[:data]).to eq(1)
|
22
|
+
expect(json[:errors]).to eq(2)
|
23
|
+
expect(json[:metadata]).to eq(3)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
context "with invalid JSON body" do
|
29
29
|
let(:body) { '"foo"' }
|
30
|
-
it
|
30
|
+
it "ensures that invalid JSON throws an exception" do
|
31
31
|
expect { subject.parse(body) }.to raise_error(Her::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "\"foo\"")')
|
32
32
|
end
|
33
33
|
end
|
34
|
-
|
35
34
|
end
|
@@ -8,8 +8,8 @@ describe Her::Model::Associations::AssociationProxy do
|
|
8
8
|
builder.use Her::Middleware::FirstLevelParseJSON
|
9
9
|
builder.use Faraday::Request::UrlEncoded
|
10
10
|
builder.adapter :test do |stub|
|
11
|
-
stub.get("/users/1") {
|
12
|
-
stub.get("/users/1/fish") {
|
11
|
+
stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias Fünke" }.to_json] }
|
12
|
+
stub.get("/users/1/fish") { [200, {}, { id: 1, name: "Tobias's Fish" }.to_json] }
|
13
13
|
end
|
14
14
|
end
|
15
15
|
spawn_model "User" do
|
@@ -26,6 +26,3 @@ describe Her::Model::Associations::AssociationProxy do
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|