her 0.8.2 → 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/.rubocop.yml +1291 -0
- data/.travis.yml +1 -0
- data/her.gemspec +1 -3
- data/lib/her/model/orm.rb +4 -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 +6 -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 +199 -158
- data/spec/model/attributes_spec.rb +98 -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 +166 -154
- data/spec/model/parse_spec.rb +77 -77
- data/spec/model/paths_spec.rb +109 -109
- data/spec/model/relation_spec.rb +68 -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
@@ -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,20 @@ 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
|
-
#context "with invalid JSON body" do
|
25
|
+
# context "with invalid JSON body" do
|
26
26
|
# let(:body) { '"foo"' }
|
27
27
|
# it 'ensures that invalid JSON throws an exception' do
|
28
28
|
# 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
29
|
# end
|
30
|
-
#end
|
31
|
-
|
30
|
+
# end
|
32
31
|
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
|
-
|
@@ -8,7 +8,11 @@ describe Her::Model::Associations do
|
|
8
8
|
|
9
9
|
context "single has_many association" do
|
10
10
|
before { Foo::User.has_many :comments }
|
11
|
-
|
11
|
+
|
12
|
+
describe "[:has_many]" do
|
13
|
+
subject { super()[:has_many] }
|
14
|
+
it { is_expected.to eql [{ name: :comments, data_key: :comments, default: [], class_name: "Comment", path: "/comments", inverse_of: nil }] }
|
15
|
+
end
|
12
16
|
end
|
13
17
|
|
14
18
|
context "multiple has_many associations" do
|
@@ -17,12 +21,19 @@ describe Her::Model::Associations do
|
|
17
21
|
Foo::User.has_many :posts
|
18
22
|
end
|
19
23
|
|
20
|
-
|
24
|
+
describe "[:has_many]" do
|
25
|
+
subject { super()[:has_many] }
|
26
|
+
it { is_expected.to eql [{ name: :comments, data_key: :comments, default: [], class_name: "Comment", path: "/comments", inverse_of: nil }, { name: :posts, data_key: :posts, default: [], class_name: "Post", path: "/posts", inverse_of: nil }] }
|
27
|
+
end
|
21
28
|
end
|
22
29
|
|
23
30
|
context "single has_one association" do
|
24
31
|
before { Foo::User.has_one :category }
|
25
|
-
|
32
|
+
|
33
|
+
describe "[:has_one]" do
|
34
|
+
subject { super()[:has_one] }
|
35
|
+
it { is_expected.to eql [{ name: :category, data_key: :category, default: nil, class_name: "Category", path: "/category" }] }
|
36
|
+
end
|
26
37
|
end
|
27
38
|
|
28
39
|
context "multiple has_one associations" do
|
@@ -31,12 +42,19 @@ describe Her::Model::Associations do
|
|
31
42
|
Foo::User.has_one :role
|
32
43
|
end
|
33
44
|
|
34
|
-
|
45
|
+
describe "[:has_one]" do
|
46
|
+
subject { super()[:has_one] }
|
47
|
+
it { is_expected.to eql [{ name: :category, data_key: :category, default: nil, class_name: "Category", path: "/category" }, { name: :role, data_key: :role, default: nil, class_name: "Role", path: "/role" }] }
|
48
|
+
end
|
35
49
|
end
|
36
50
|
|
37
51
|
context "single belongs_to association" do
|
38
52
|
before { Foo::User.belongs_to :organization }
|
39
|
-
|
53
|
+
|
54
|
+
describe "[:belongs_to]" do
|
55
|
+
subject { super()[:belongs_to] }
|
56
|
+
it { is_expected.to eql [{ name: :organization, data_key: :organization, default: nil, class_name: "Organization", foreign_key: "organization_id", path: "/organizations/:id" }] }
|
57
|
+
end
|
40
58
|
end
|
41
59
|
|
42
60
|
context "multiple belongs_to association" do
|
@@ -45,7 +63,10 @@ describe Her::Model::Associations do
|
|
45
63
|
Foo::User.belongs_to :family
|
46
64
|
end
|
47
65
|
|
48
|
-
|
66
|
+
describe "[:belongs_to]" do
|
67
|
+
subject { super()[:belongs_to] }
|
68
|
+
it { is_expected.to eql [{ name: :organization, data_key: :organization, default: nil, class_name: "Organization", foreign_key: "organization_id", path: "/organizations/:id" }, { name: :family, data_key: :family, default: nil, class_name: "Family", foreign_key: "family_id", path: "/families/:id" }] }
|
69
|
+
end
|
49
70
|
end
|
50
71
|
end
|
51
72
|
|
@@ -55,56 +76,76 @@ describe Her::Model::Associations do
|
|
55
76
|
|
56
77
|
context "in base class" do
|
57
78
|
context "single has_many association" do
|
58
|
-
before { Foo::User.has_many :comments, :
|
59
|
-
|
79
|
+
before { Foo::User.has_many :comments, class_name: "Post", inverse_of: :admin, data_key: :user_comments, default: {} }
|
80
|
+
|
81
|
+
describe "[:has_many]" do
|
82
|
+
subject { super()[:has_many] }
|
83
|
+
it { is_expected.to eql [{ name: :comments, data_key: :user_comments, default: {}, class_name: "Post", path: "/comments", inverse_of: :admin }] }
|
84
|
+
end
|
60
85
|
end
|
61
86
|
|
62
87
|
context "single has_one association" do
|
63
|
-
before { Foo::User.has_one :category, :
|
64
|
-
|
88
|
+
before { Foo::User.has_one :category, class_name: "Topic", foreign_key: "topic_id", data_key: :topic, default: nil }
|
89
|
+
|
90
|
+
describe "[:has_one]" do
|
91
|
+
subject { super()[:has_one] }
|
92
|
+
it { is_expected.to eql [{ name: :category, data_key: :topic, default: nil, class_name: "Topic", foreign_key: "topic_id", path: "/category" }] }
|
93
|
+
end
|
65
94
|
end
|
66
95
|
|
67
96
|
context "single belongs_to association" do
|
68
|
-
before { Foo::User.belongs_to :organization, :
|
69
|
-
|
97
|
+
before { Foo::User.belongs_to :organization, class_name: "Business", foreign_key: "org_id", data_key: :org, default: true }
|
98
|
+
|
99
|
+
describe "[:belongs_to]" do
|
100
|
+
subject { super()[:belongs_to] }
|
101
|
+
it { is_expected.to eql [{ name: :organization, data_key: :org, default: true, class_name: "Business", foreign_key: "org_id", path: "/organizations/:id" }] }
|
102
|
+
end
|
70
103
|
end
|
71
104
|
end
|
72
105
|
|
73
106
|
context "in parent class" do
|
74
|
-
before { Foo::User.has_many :comments, :
|
107
|
+
before { Foo::User.has_many :comments, class_name: "Post" }
|
75
108
|
|
76
109
|
describe "associations accessor" do
|
77
110
|
subject { Class.new(Foo::User).associations }
|
78
|
-
|
79
|
-
|
111
|
+
|
112
|
+
describe "#object_id" do
|
113
|
+
subject { super().object_id }
|
114
|
+
it { is_expected.not_to eql Foo::User.associations.object_id }
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "[:has_many]" do
|
118
|
+
subject { super()[:has_many] }
|
119
|
+
it { is_expected.to eql [{ name: :comments, data_key: :comments, default: [], class_name: "Post", path: "/comments", inverse_of: nil }] }
|
120
|
+
end
|
80
121
|
end
|
81
122
|
end
|
82
123
|
end
|
83
124
|
|
84
125
|
context "handling associations without details" do
|
85
126
|
before do
|
86
|
-
Her::API.setup :
|
127
|
+
Her::API.setup url: "https://api.example.com" do |builder|
|
87
128
|
builder.use Her::Middleware::FirstLevelParseJSON
|
88
129
|
builder.use Faraday::Request::UrlEncoded
|
89
130
|
builder.adapter :test do |stub|
|
90
|
-
stub.get("/users/1") {
|
91
|
-
stub.get("/users/2") {
|
92
|
-
stub.get("/users/1/comments") {
|
93
|
-
stub.get("/users/2/comments") {
|
94
|
-
stub.get("/users/2/comments/5") {
|
95
|
-
stub.get("/users/2/role") {
|
96
|
-
stub.get("/users/1/role") {
|
97
|
-
stub.get("/users/1/posts") {
|
98
|
-
stub.get("/organizations/1") {
|
99
|
-
stub.post("/users") {
|
100
|
-
stub.put("/users/5") {
|
101
|
-
stub.delete("/users/5") {
|
131
|
+
stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias Fünke", comments: [{ comment: { id: 2, body: "Tobias, you blow hard!", user_id: 1 } }, { comment: { id: 3, body: "I wouldn't mind kissing that man between the cheeks, so to speak", user_id: 1 } }], role: { id: 1, body: "Admin" }, organization: { id: 1, name: "Bluth Company" }, organization_id: 1 }.to_json] }
|
132
|
+
stub.get("/users/2") { [200, {}, { id: 2, name: "Lindsay Fünke", organization_id: 2 }.to_json] }
|
133
|
+
stub.get("/users/1/comments") { [200, {}, [{ comment: { id: 4, body: "They're having a FIRESALE?" } }].to_json] }
|
134
|
+
stub.get("/users/2/comments") { [200, {}, [{ comment: { id: 4, body: "They're having a FIRESALE?" } }, { comment: { id: 5, body: "Is this the tiny town from Footloose?" } }].to_json] }
|
135
|
+
stub.get("/users/2/comments/5") { [200, {}, { comment: { id: 5, body: "Is this the tiny town from Footloose?" } }.to_json] }
|
136
|
+
stub.get("/users/2/role") { [200, {}, { id: 2, body: "User" }.to_json] }
|
137
|
+
stub.get("/users/1/role") { [200, {}, { id: 3, body: "User" }.to_json] }
|
138
|
+
stub.get("/users/1/posts") { [200, {}, [{ id: 1, body: "blogging stuff", admin_id: 1 }].to_json] }
|
139
|
+
stub.get("/organizations/1") { [200, {}, { organization: { id: 1, name: "Bluth Company Foo" } }.to_json] }
|
140
|
+
stub.post("/users") { [200, {}, { id: 5, name: "Mr. Krabs", comments: [{ comment: { id: 99, body: "Rodríguez, nasibisibusi?", user_id: 5 } }], role: { id: 1, body: "Admin" }, organization: { id: 3, name: "Krusty Krab" }, organization_id: 3 }.to_json] }
|
141
|
+
stub.put("/users/5") { [200, {}, { id: 5, name: "Clancy Brown", comments: [{ comment: { id: 99, body: "Rodríguez, nasibisibusi?", user_id: 5 } }], role: { id: 1, body: "Admin" }, organization: { id: 3, name: "Krusty Krab" }, organization_id: 3 }.to_json] }
|
142
|
+
stub.delete("/users/5") { [200, {}, { id: 5, name: "Clancy Brown", comments: [{ comment: { id: 99, body: "Rodríguez, nasibisibusi?", user_id: 5 } }], role: { id: 1, body: "Admin" }, organization: { id: 3, name: "Krusty Krab" }, organization_id: 3 }.to_json] }
|
102
143
|
|
103
144
|
stub.get("/organizations/2") do |env|
|
104
145
|
if env[:params]["admin"] == "true"
|
105
|
-
[200, {}, { :
|
146
|
+
[200, {}, { organization: { id: 2, name: "Bluth Company (admin)" } }.to_json]
|
106
147
|
else
|
107
|
-
[200, {}, { :
|
148
|
+
[200, {}, { organization: { id: 2, name: "Bluth Company" } }.to_json]
|
108
149
|
end
|
109
150
|
end
|
110
151
|
end
|
@@ -114,14 +155,14 @@ describe Her::Model::Associations do
|
|
114
155
|
has_many :comments, class_name: "Foo::Comment"
|
115
156
|
has_one :role
|
116
157
|
belongs_to :organization
|
117
|
-
has_many :posts, :
|
158
|
+
has_many :posts, inverse_of: :admin
|
118
159
|
end
|
119
160
|
spawn_model "Foo::Comment" do
|
120
161
|
belongs_to :user
|
121
162
|
parse_root_in_json true
|
122
163
|
end
|
123
164
|
spawn_model "Foo::Post" do
|
124
|
-
belongs_to :admin, :
|
165
|
+
belongs_to :admin, class_name: "Foo::User"
|
125
166
|
end
|
126
167
|
|
127
168
|
spawn_model "Foo::Organization" do
|
@@ -136,136 +177,136 @@ describe Her::Model::Associations do
|
|
136
177
|
end
|
137
178
|
|
138
179
|
let(:user_with_included_data_after_create) { Foo::User.create }
|
139
|
-
let(:user_with_included_data_after_save_existing) { Foo::User.save_existing(5, :
|
140
|
-
let(:user_with_included_data_after_destroy) { Foo::User.new(:
|
141
|
-
let(:comment_without_included_parent_data) { Foo::Comment.new(:
|
180
|
+
let(:user_with_included_data_after_save_existing) { Foo::User.save_existing(5, name: "Clancy Brown") }
|
181
|
+
let(:user_with_included_data_after_destroy) { Foo::User.new(id: 5).destroy }
|
182
|
+
let(:comment_without_included_parent_data) { Foo::Comment.new(id: 7, user_id: 1) }
|
142
183
|
|
143
184
|
it "maps an array of included data through has_many" do
|
144
|
-
@user_with_included_data.comments.first.
|
145
|
-
@user_with_included_data.comments.length.
|
146
|
-
@user_with_included_data.comments.first.id.
|
147
|
-
@user_with_included_data.comments.first.body.
|
185
|
+
expect(@user_with_included_data.comments.first).to be_a(Foo::Comment)
|
186
|
+
expect(@user_with_included_data.comments.length).to eq(2)
|
187
|
+
expect(@user_with_included_data.comments.first.id).to eq(2)
|
188
|
+
expect(@user_with_included_data.comments.first.body).to eq("Tobias, you blow hard!")
|
148
189
|
end
|
149
190
|
|
150
191
|
it "does not refetch the parents models data if they have been fetched before" do
|
151
|
-
@user_with_included_data.comments.first.user.object_id.
|
192
|
+
expect(@user_with_included_data.comments.first.user.object_id).to eq(@user_with_included_data.object_id)
|
152
193
|
end
|
153
194
|
|
154
195
|
it "does fetch the parent models data only once" do
|
155
|
-
comment_without_included_parent_data.user.object_id.
|
196
|
+
expect(comment_without_included_parent_data.user.object_id).to eq(comment_without_included_parent_data.user.object_id)
|
156
197
|
end
|
157
198
|
|
158
199
|
it "does fetch the parent models data that was cached if called with parameters" do
|
159
|
-
comment_without_included_parent_data.user.object_id.
|
200
|
+
expect(comment_without_included_parent_data.user.object_id).not_to eq(comment_without_included_parent_data.user.where(a: 2).object_id)
|
160
201
|
end
|
161
202
|
|
162
203
|
it "uses the given inverse_of key to set the parent model" do
|
163
|
-
@user_with_included_data.posts.first.admin.object_id.
|
204
|
+
expect(@user_with_included_data.posts.first.admin.object_id).to eq(@user_with_included_data.object_id)
|
164
205
|
end
|
165
206
|
|
166
207
|
it "fetches data that was not included through has_many" do
|
167
|
-
@user_without_included_data.comments.first.
|
168
|
-
@user_without_included_data.comments.length.
|
169
|
-
@user_without_included_data.comments.first.id.
|
170
|
-
@user_without_included_data.comments.first.body.
|
208
|
+
expect(@user_without_included_data.comments.first).to be_a(Foo::Comment)
|
209
|
+
expect(@user_without_included_data.comments.length).to eq(2)
|
210
|
+
expect(@user_without_included_data.comments.first.id).to eq(4)
|
211
|
+
expect(@user_without_included_data.comments.first.body).to eq("They're having a FIRESALE?")
|
171
212
|
end
|
172
213
|
|
173
214
|
it "fetches has_many data even if it was included, only if called with parameters" do
|
174
|
-
@user_with_included_data.comments.where(:
|
215
|
+
expect(@user_with_included_data.comments.where(foo_id: 1).length).to eq(1)
|
175
216
|
end
|
176
217
|
|
177
218
|
it "fetches data that was not included through has_many only once" do
|
178
|
-
@user_without_included_data.comments.first.object_id.
|
219
|
+
expect(@user_without_included_data.comments.first.object_id).to eq(@user_without_included_data.comments.first.object_id)
|
179
220
|
end
|
180
221
|
|
181
222
|
it "fetches data that was cached through has_many if called with parameters" do
|
182
|
-
@user_without_included_data.comments.first.object_id.
|
223
|
+
expect(@user_without_included_data.comments.first.object_id).not_to eq(@user_without_included_data.comments.where(foo_id: 1).first.object_id)
|
183
224
|
end
|
184
225
|
|
185
226
|
it "maps an array of included data through has_one" do
|
186
|
-
@user_with_included_data.role.
|
187
|
-
@user_with_included_data.role.object_id.
|
188
|
-
@user_with_included_data.role.id.
|
189
|
-
@user_with_included_data.role.body.
|
227
|
+
expect(@user_with_included_data.role).to be_a(Foo::Role)
|
228
|
+
expect(@user_with_included_data.role.object_id).to eq(@user_with_included_data.role.object_id)
|
229
|
+
expect(@user_with_included_data.role.id).to eq(1)
|
230
|
+
expect(@user_with_included_data.role.body).to eq("Admin")
|
190
231
|
end
|
191
232
|
|
192
233
|
it "fetches data that was not included through has_one" do
|
193
|
-
@user_without_included_data.role.
|
194
|
-
@user_without_included_data.role.id.
|
195
|
-
@user_without_included_data.role.body.
|
234
|
+
expect(@user_without_included_data.role).to be_a(Foo::Role)
|
235
|
+
expect(@user_without_included_data.role.id).to eq(2)
|
236
|
+
expect(@user_without_included_data.role.body).to eq("User")
|
196
237
|
end
|
197
238
|
|
198
239
|
it "fetches has_one data even if it was included, only if called with parameters" do
|
199
|
-
@user_with_included_data.role.where(:
|
240
|
+
expect(@user_with_included_data.role.where(foo_id: 2).id).to eq(3)
|
200
241
|
end
|
201
242
|
|
202
243
|
it "maps an array of included data through belongs_to" do
|
203
|
-
@user_with_included_data.organization.
|
204
|
-
@user_with_included_data.organization.id.
|
205
|
-
@user_with_included_data.organization.name.
|
244
|
+
expect(@user_with_included_data.organization).to be_a(Foo::Organization)
|
245
|
+
expect(@user_with_included_data.organization.id).to eq(1)
|
246
|
+
expect(@user_with_included_data.organization.name).to eq("Bluth Company")
|
206
247
|
end
|
207
248
|
|
208
249
|
it "fetches data that was not included through belongs_to" do
|
209
|
-
@user_without_included_data.organization.
|
210
|
-
@user_without_included_data.organization.id.
|
211
|
-
@user_without_included_data.organization.name.
|
250
|
+
expect(@user_without_included_data.organization).to be_a(Foo::Organization)
|
251
|
+
expect(@user_without_included_data.organization.id).to eq(2)
|
252
|
+
expect(@user_without_included_data.organization.name).to eq("Bluth Company")
|
212
253
|
end
|
213
254
|
|
214
255
|
it "returns nil if the foreign key is nil" do
|
215
|
-
@user_without_organization_and_not_persisted.organization.
|
256
|
+
expect(@user_without_organization_and_not_persisted.organization).to be_nil
|
216
257
|
end
|
217
258
|
|
218
259
|
it "fetches belongs_to data even if it was included, only if called with parameters" do
|
219
|
-
@user_with_included_data.organization.where(:
|
260
|
+
expect(@user_with_included_data.organization.where(foo_id: 1).name).to eq("Bluth Company Foo")
|
220
261
|
end
|
221
262
|
|
222
263
|
it "can tell if it has a association" do
|
223
|
-
@user_without_included_data.has_association?(:unknown_association).
|
224
|
-
@user_without_included_data.has_association?(:organization).
|
264
|
+
expect(@user_without_included_data.has_association?(:unknown_association)).to be false
|
265
|
+
expect(@user_without_included_data.has_association?(:organization)).to be true
|
225
266
|
end
|
226
267
|
|
227
268
|
it "fetches the resource corresponding to a named association" do
|
228
|
-
@user_without_included_data.get_association(:unknown_association).
|
229
|
-
@user_without_included_data.get_association(:organization).name.
|
269
|
+
expect(@user_without_included_data.get_association(:unknown_association)).to be_nil
|
270
|
+
expect(@user_without_included_data.get_association(:organization).name).to eq("Bluth Company")
|
230
271
|
end
|
231
272
|
|
232
273
|
it "pass query string parameters when additional arguments are passed" do
|
233
|
-
@user_without_included_data.organization.where(:
|
234
|
-
@user_without_included_data.organization.name.
|
274
|
+
expect(@user_without_included_data.organization.where(admin: true).name).to eq("Bluth Company (admin)")
|
275
|
+
expect(@user_without_included_data.organization.name).to eq("Bluth Company")
|
235
276
|
end
|
236
277
|
|
237
278
|
it "fetches data with the specified id when calling find" do
|
238
279
|
comment = @user_without_included_data.comments.find(5)
|
239
|
-
comment.
|
240
|
-
comment.id.
|
280
|
+
expect(comment).to be_a(Foo::Comment)
|
281
|
+
expect(comment.id).to eq(5)
|
241
282
|
end
|
242
283
|
|
243
284
|
it "'s associations responds to #empty?" do
|
244
|
-
@user_without_included_data.organization.respond_to?(:empty?).
|
245
|
-
@user_without_included_data.organization.
|
285
|
+
expect(@user_without_included_data.organization.respond_to?(:empty?)).to be_truthy
|
286
|
+
expect(@user_without_included_data.organization).not_to be_empty
|
246
287
|
end
|
247
288
|
|
248
|
-
it
|
289
|
+
it "includes has_many relationships in params by default" do
|
249
290
|
params = @user_with_included_data.to_params
|
250
|
-
params[:comments].
|
251
|
-
params[:comments].length.
|
291
|
+
expect(params[:comments]).to be_kind_of(Array)
|
292
|
+
expect(params[:comments].length).to eq(2)
|
252
293
|
end
|
253
294
|
|
254
295
|
[:create, :save_existing, :destroy].each do |type|
|
255
296
|
context "after #{type}" do
|
256
|
-
let(:subject) {
|
297
|
+
let(:subject) { send("user_with_included_data_after_#{type}") }
|
257
298
|
|
258
299
|
it "maps an array of included data through has_many" do
|
259
|
-
subject.comments.first.
|
260
|
-
subject.comments.length.
|
261
|
-
subject.comments.first.id.
|
262
|
-
subject.comments.first.body.
|
300
|
+
expect(subject.comments.first).to be_a(Foo::Comment)
|
301
|
+
expect(subject.comments.length).to eq(1)
|
302
|
+
expect(subject.comments.first.id).to eq(99)
|
303
|
+
expect(subject.comments.first.body).to eq("Rodríguez, nasibisibusi?")
|
263
304
|
end
|
264
305
|
|
265
306
|
it "maps an array of included data through has_one" do
|
266
|
-
subject.role.
|
267
|
-
subject.role.id.
|
268
|
-
subject.role.body.
|
307
|
+
expect(subject.role).to be_a(Foo::Role)
|
308
|
+
expect(subject.role.id).to eq(1)
|
309
|
+
expect(subject.role.body).to eq("Admin")
|
269
310
|
end
|
270
311
|
end
|
271
312
|
end
|
@@ -273,29 +314,29 @@ describe Her::Model::Associations do
|
|
273
314
|
|
274
315
|
context "handling associations with details in active_model_serializers format" do
|
275
316
|
before do
|
276
|
-
Her::API.setup :
|
317
|
+
Her::API.setup url: "https://api.example.com" do |builder|
|
277
318
|
builder.use Her::Middleware::FirstLevelParseJSON
|
278
319
|
builder.use Faraday::Request::UrlEncoded
|
279
320
|
builder.adapter :test do |stub|
|
280
|
-
stub.get("/users/1") {
|
281
|
-
stub.get("/users/2") {
|
282
|
-
stub.get("/users/1/comments") {
|
283
|
-
stub.get("/users/2/comments") {
|
284
|
-
stub.get("/users/2/comments/5") {
|
285
|
-
stub.get("/organizations/1") {
|
321
|
+
stub.get("/users/1") { [200, {}, { user: { id: 1, name: "Tobias Fünke", comments: [{ id: 2, body: "Tobias, you blow hard!", user_id: 1 }, { id: 3, body: "I wouldn't mind kissing that man between the cheeks, so to speak", user_id: 1 }], role: { id: 1, body: "Admin" }, organization: { id: 1, name: "Bluth Company" }, organization_id: 1 } }.to_json] }
|
322
|
+
stub.get("/users/2") { [200, {}, { user: { id: 2, name: "Lindsay Fünke", organization_id: 1 } }.to_json] }
|
323
|
+
stub.get("/users/1/comments") { [200, {}, { comments: [{ id: 4, body: "They're having a FIRESALE?" }] }.to_json] }
|
324
|
+
stub.get("/users/2/comments") { [200, {}, { comments: [{ id: 4, body: "They're having a FIRESALE?" }, { id: 5, body: "Is this the tiny town from Footloose?" }] }.to_json] }
|
325
|
+
stub.get("/users/2/comments/5") { [200, {}, { comment: { id: 5, body: "Is this the tiny town from Footloose?" } }.to_json] }
|
326
|
+
stub.get("/organizations/1") { [200, {}, { organization: { id: 1, name: "Bluth Company Foo" } }.to_json] }
|
286
327
|
end
|
287
328
|
end
|
288
329
|
spawn_model "Foo::User" do
|
289
|
-
parse_root_in_json true, :
|
330
|
+
parse_root_in_json true, format: :active_model_serializers
|
290
331
|
has_many :comments, class_name: "Foo::Comment"
|
291
332
|
belongs_to :organization
|
292
333
|
end
|
293
334
|
spawn_model "Foo::Comment" do
|
294
335
|
belongs_to :user
|
295
|
-
parse_root_in_json true, :
|
336
|
+
parse_root_in_json true, format: :active_model_serializers
|
296
337
|
end
|
297
338
|
spawn_model "Foo::Organization" do
|
298
|
-
parse_root_in_json true, :
|
339
|
+
parse_root_in_json true, format: :active_model_serializers
|
299
340
|
end
|
300
341
|
|
301
342
|
@user_with_included_data = Foo::User.find(1)
|
@@ -303,72 +344,72 @@ describe Her::Model::Associations do
|
|
303
344
|
end
|
304
345
|
|
305
346
|
it "maps an array of included data through has_many" do
|
306
|
-
@user_with_included_data.comments.first.
|
307
|
-
@user_with_included_data.comments.length.
|
308
|
-
@user_with_included_data.comments.first.id.
|
309
|
-
@user_with_included_data.comments.first.body.
|
347
|
+
expect(@user_with_included_data.comments.first).to be_a(Foo::Comment)
|
348
|
+
expect(@user_with_included_data.comments.length).to eq(2)
|
349
|
+
expect(@user_with_included_data.comments.first.id).to eq(2)
|
350
|
+
expect(@user_with_included_data.comments.first.body).to eq("Tobias, you blow hard!")
|
310
351
|
end
|
311
352
|
|
312
353
|
it "does not refetch the parents models data if they have been fetched before" do
|
313
|
-
@user_with_included_data.comments.first.user.object_id.
|
354
|
+
expect(@user_with_included_data.comments.first.user.object_id).to eq(@user_with_included_data.object_id)
|
314
355
|
end
|
315
356
|
|
316
357
|
it "fetches data that was not included through has_many" do
|
317
|
-
@user_without_included_data.comments.first.
|
318
|
-
@user_without_included_data.comments.length.
|
319
|
-
@user_without_included_data.comments.first.id.
|
320
|
-
@user_without_included_data.comments.first.body.
|
358
|
+
expect(@user_without_included_data.comments.first).to be_a(Foo::Comment)
|
359
|
+
expect(@user_without_included_data.comments.length).to eq(2)
|
360
|
+
expect(@user_without_included_data.comments.first.id).to eq(4)
|
361
|
+
expect(@user_without_included_data.comments.first.body).to eq("They're having a FIRESALE?")
|
321
362
|
end
|
322
363
|
|
323
364
|
it "fetches has_many data even if it was included, only if called with parameters" do
|
324
|
-
@user_with_included_data.comments.where(:
|
365
|
+
expect(@user_with_included_data.comments.where(foo_id: 1).length).to eq(1)
|
325
366
|
end
|
326
367
|
|
327
368
|
it "maps an array of included data through belongs_to" do
|
328
|
-
@user_with_included_data.organization.
|
329
|
-
@user_with_included_data.organization.id.
|
330
|
-
@user_with_included_data.organization.name.
|
369
|
+
expect(@user_with_included_data.organization).to be_a(Foo::Organization)
|
370
|
+
expect(@user_with_included_data.organization.id).to eq(1)
|
371
|
+
expect(@user_with_included_data.organization.name).to eq("Bluth Company")
|
331
372
|
end
|
332
373
|
|
333
374
|
it "fetches data that was not included through belongs_to" do
|
334
|
-
@user_without_included_data.organization.
|
335
|
-
@user_without_included_data.organization.id.
|
336
|
-
@user_without_included_data.organization.name.
|
375
|
+
expect(@user_without_included_data.organization).to be_a(Foo::Organization)
|
376
|
+
expect(@user_without_included_data.organization.id).to eq(1)
|
377
|
+
expect(@user_without_included_data.organization.name).to eq("Bluth Company Foo")
|
337
378
|
end
|
338
379
|
|
339
380
|
it "fetches belongs_to data even if it was included, only if called with parameters" do
|
340
|
-
@user_with_included_data.organization.where(:
|
381
|
+
expect(@user_with_included_data.organization.where(foo_id: 1).name).to eq("Bluth Company Foo")
|
341
382
|
end
|
342
383
|
|
343
384
|
it "fetches data with the specified id when calling find" do
|
344
385
|
comment = @user_without_included_data.comments.find(5)
|
345
|
-
comment.
|
346
|
-
comment.id.
|
386
|
+
expect(comment).to be_a(Foo::Comment)
|
387
|
+
expect(comment.id).to eq(5)
|
347
388
|
end
|
348
389
|
|
349
|
-
it
|
390
|
+
it "includes has_many relationships in params by default" do
|
350
391
|
params = @user_with_included_data.to_params
|
351
|
-
params[:comments].
|
352
|
-
params[:comments].length.
|
392
|
+
expect(params[:comments]).to be_kind_of(Array)
|
393
|
+
expect(params[:comments].length).to eq(2)
|
353
394
|
end
|
354
395
|
end
|
355
396
|
|
356
397
|
context "handling associations with details" do
|
357
398
|
before do
|
358
|
-
Her::API.setup :
|
399
|
+
Her::API.setup url: "https://api.example.com" do |builder|
|
359
400
|
builder.use Her::Middleware::FirstLevelParseJSON
|
360
401
|
builder.use Faraday::Request::UrlEncoded
|
361
402
|
builder.adapter :test do |stub|
|
362
|
-
stub.get("/users/1") {
|
363
|
-
stub.get("/users/4") {
|
364
|
-
stub.get("/users/2") {
|
365
|
-
stub.get("/users/3") {
|
366
|
-
stub.get("/companies/1") {
|
403
|
+
stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias Fünke", organization: { id: 1, name: "Bluth Company Inc." }, organization_id: 1 }.to_json] }
|
404
|
+
stub.get("/users/4") { [200, {}, { id: 1, name: "Tobias Fünke", organization: { id: 1, name: "Bluth Company Inc." } }.to_json] }
|
405
|
+
stub.get("/users/2") { [200, {}, { id: 2, name: "Lindsay Fünke", organization_id: 1 }.to_json] }
|
406
|
+
stub.get("/users/3") { [200, {}, { id: 2, name: "Lindsay Fünke", company: nil }.to_json] }
|
407
|
+
stub.get("/companies/1") { [200, {}, { id: 1, name: "Bluth Company" }.to_json] }
|
367
408
|
end
|
368
409
|
end
|
369
410
|
|
370
411
|
spawn_model "Foo::User" do
|
371
|
-
belongs_to :company, :
|
412
|
+
belongs_to :company, path: "/organizations/:id", foreign_key: :organization_id, data_key: :organization
|
372
413
|
end
|
373
414
|
|
374
415
|
spawn_model "Foo::Company"
|
@@ -380,23 +421,23 @@ describe Her::Model::Associations do
|
|
380
421
|
end
|
381
422
|
|
382
423
|
it "maps an array of included data through belongs_to" do
|
383
|
-
@user_with_included_data.company.
|
384
|
-
@user_with_included_data.company.id.
|
385
|
-
@user_with_included_data.company.name.
|
424
|
+
expect(@user_with_included_data.company).to be_a(Foo::Company)
|
425
|
+
expect(@user_with_included_data.company.id).to eq(1)
|
426
|
+
expect(@user_with_included_data.company.name).to eq("Bluth Company Inc.")
|
386
427
|
end
|
387
428
|
|
388
429
|
it "does not map included data if it’s nil" do
|
389
|
-
@user_with_included_nil_data.company.
|
430
|
+
expect(@user_with_included_nil_data.company).to be_nil
|
390
431
|
end
|
391
432
|
|
392
433
|
it "fetches data that was not included through belongs_to" do
|
393
|
-
@user_without_included_data.company.
|
394
|
-
@user_without_included_data.company.id.
|
395
|
-
@user_without_included_data.company.name.
|
434
|
+
expect(@user_without_included_data.company).to be_a(Foo::Company)
|
435
|
+
expect(@user_without_included_data.company.id).to eq(1)
|
436
|
+
expect(@user_without_included_data.company.name).to eq("Bluth Company")
|
396
437
|
end
|
397
438
|
|
398
439
|
it "does not require foreugn key to have nested object" do
|
399
|
-
@user_with_included_data_but_no_fk.company.name.
|
440
|
+
expect(@user_with_included_data_but_no_fk.company.name).to eq("Bluth Company Inc.")
|
400
441
|
end
|
401
442
|
end
|
402
443
|
|
@@ -420,30 +461,30 @@ describe Her::Model::Associations do
|
|
420
461
|
subject { user_with_role.role }
|
421
462
|
|
422
463
|
it "doesnt mask the object's basic methods" do
|
423
|
-
subject.class.
|
464
|
+
expect(subject.class).to eq(Foo::Role)
|
424
465
|
end
|
425
466
|
|
426
467
|
it "doesnt mask core methods like extend" do
|
427
468
|
committer = Module.new
|
428
|
-
subject.extend
|
429
|
-
associated_value.
|
469
|
+
subject.extend committer
|
470
|
+
expect(associated_value).to be_kind_of committer
|
430
471
|
end
|
431
472
|
|
432
473
|
it "can return the association object" do
|
433
|
-
subject.association.
|
474
|
+
expect(subject.association).to be_kind_of Her::Model::Associations::Association
|
434
475
|
end
|
435
476
|
|
436
477
|
it "still can call fetch via the association" do
|
437
|
-
subject.association.fetch.
|
478
|
+
expect(subject.association.fetch).to eq associated_value
|
438
479
|
end
|
439
480
|
|
440
481
|
it "calls missing methods on associated value" do
|
441
|
-
subject.present
|
482
|
+
expect(subject.present?).to eq("of_course")
|
442
483
|
end
|
443
484
|
|
444
485
|
it "can use association methods like where" do
|
445
|
-
subject.where(role:
|
446
|
-
params.
|
486
|
+
expect(subject.where(role: "committer").association
|
487
|
+
.params).to include :role
|
447
488
|
end
|
448
489
|
end
|
449
490
|
|
@@ -457,20 +498,20 @@ describe Her::Model::Associations do
|
|
457
498
|
|
458
499
|
context "with #build" do
|
459
500
|
it "takes the parent primary key" do
|
460
|
-
@comment = Foo::User.new(:
|
461
|
-
@comment.body.
|
462
|
-
@comment.user_id.
|
501
|
+
@comment = Foo::User.new(id: 10).comments.build(body: "Hello!")
|
502
|
+
expect(@comment.body).to eq("Hello!")
|
503
|
+
expect(@comment.user_id).to eq(10)
|
463
504
|
end
|
464
505
|
end
|
465
506
|
|
466
507
|
context "with #create" do
|
467
508
|
before do
|
468
|
-
Her::API.setup :
|
509
|
+
Her::API.setup url: "https://api.example.com" do |builder|
|
469
510
|
builder.use Her::Middleware::FirstLevelParseJSON
|
470
511
|
builder.use Faraday::Request::UrlEncoded
|
471
512
|
builder.adapter :test do |stub|
|
472
|
-
stub.get("/users/10") {
|
473
|
-
stub.post("/comments") { |env| [200, {}, { :
|
513
|
+
stub.get("/users/10") { [200, {}, { id: 10 }.to_json] }
|
514
|
+
stub.post("/comments") { |env| [200, {}, { id: 1, body: Faraday::Utils.parse_query(env[:body])["body"], user_id: Faraday::Utils.parse_query(env[:body])["user_id"].to_i }.to_json] }
|
474
515
|
end
|
475
516
|
end
|
476
517
|
|
@@ -480,24 +521,24 @@ describe Her::Model::Associations do
|
|
480
521
|
|
481
522
|
it "takes the parent primary key and saves the resource" do
|
482
523
|
@user = Foo::User.find(10)
|
483
|
-
@comment = @user.comments.create(:
|
484
|
-
@comment.id.
|
485
|
-
@comment.body.
|
486
|
-
@comment.user_id.
|
487
|
-
@user.comments.
|
524
|
+
@comment = @user.comments.create(body: "Hello!")
|
525
|
+
expect(@comment.id).to eq(1)
|
526
|
+
expect(@comment.body).to eq("Hello!")
|
527
|
+
expect(@comment.user_id).to eq(10)
|
528
|
+
expect(@user.comments).to eq([@comment])
|
488
529
|
end
|
489
530
|
end
|
490
531
|
|
491
532
|
context "with #new" do
|
492
533
|
it "creates nested models from hash attibutes" do
|
493
|
-
user = Foo::User.new(:
|
494
|
-
user.comments.first.text.
|
534
|
+
user = Foo::User.new(name: "vic", comments: [{ text: "hello" }])
|
535
|
+
expect(user.comments.first.text).to eq("hello")
|
495
536
|
end
|
496
537
|
|
497
538
|
it "assigns nested models if given as already constructed objects" do
|
498
|
-
bye = Foo::Comment.new(:
|
499
|
-
user = Foo::User.new(:
|
500
|
-
user.comments.first.text.
|
539
|
+
bye = Foo::Comment.new(text: "goodbye")
|
540
|
+
user = Foo::User.new(name: "vic", comments: [bye])
|
541
|
+
expect(user.comments.first.text).to eq("goodbye")
|
501
542
|
end
|
502
543
|
end
|
503
544
|
end
|