him 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/.github/workflows/ci.yml +40 -0
- data/.gitignore +6 -0
- data/.qlty/qlty.toml +57 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.yardopts +2 -0
- data/CONTRIBUTING.md +26 -0
- data/Gemfile +2 -0
- data/LICENSE +8 -0
- data/README.md +1007 -0
- data/Rakefile +11 -0
- data/UPGRADE.md +101 -0
- data/gemfiles/Gemfile.activemodel-6.1 +6 -0
- data/gemfiles/Gemfile.activemodel-7.0 +6 -0
- data/gemfiles/Gemfile.activemodel-7.1 +6 -0
- data/gemfiles/Gemfile.activemodel-7.2 +6 -0
- data/gemfiles/Gemfile.activemodel-8.0 +6 -0
- data/him.gemspec +28 -0
- data/lib/him/api.rb +121 -0
- data/lib/him/collection.rb +21 -0
- data/lib/him/errors.rb +29 -0
- data/lib/him/json_api/model.rb +42 -0
- data/lib/him/middleware/accept_json.rb +18 -0
- data/lib/him/middleware/first_level_parse_json.rb +37 -0
- data/lib/him/middleware/json_api_parser.rb +65 -0
- data/lib/him/middleware/parse_json.rb +22 -0
- data/lib/him/middleware/second_level_parse_json.rb +37 -0
- data/lib/him/middleware.rb +12 -0
- data/lib/him/model/associations/association.rb +147 -0
- data/lib/him/model/associations/association_proxy.rb +47 -0
- data/lib/him/model/associations/belongs_to_association.rb +95 -0
- data/lib/him/model/associations/has_many_association.rb +113 -0
- data/lib/him/model/associations/has_one_association.rb +79 -0
- data/lib/him/model/associations.rb +141 -0
- data/lib/him/model/attributes.rb +337 -0
- data/lib/him/model/base.rb +33 -0
- data/lib/him/model/http.rb +113 -0
- data/lib/him/model/introspection.rb +77 -0
- data/lib/him/model/nested_attributes.rb +45 -0
- data/lib/him/model/orm.rb +306 -0
- data/lib/him/model/parse.rb +224 -0
- data/lib/him/model/paths.rb +125 -0
- data/lib/him/model/relation.rb +212 -0
- data/lib/him/model.rb +79 -0
- data/lib/him/version.rb +3 -0
- data/lib/him.rb +22 -0
- data/spec/api_spec.rb +120 -0
- data/spec/collection_spec.rb +70 -0
- data/spec/json_api/model_spec.rb +260 -0
- data/spec/middleware/accept_json_spec.rb +11 -0
- data/spec/middleware/first_level_parse_json_spec.rb +63 -0
- data/spec/middleware/json_api_parser_spec.rb +52 -0
- data/spec/middleware/second_level_parse_json_spec.rb +35 -0
- data/spec/model/associations/association_proxy_spec.rb +29 -0
- data/spec/model/associations_spec.rb +1010 -0
- data/spec/model/attributes_spec.rb +384 -0
- data/spec/model/callbacks_spec.rb +194 -0
- data/spec/model/dirty_spec.rb +133 -0
- data/spec/model/http_spec.rb +187 -0
- data/spec/model/introspection_spec.rb +110 -0
- data/spec/model/nested_attributes_spec.rb +135 -0
- data/spec/model/orm_spec.rb +717 -0
- data/spec/model/parse_spec.rb +619 -0
- data/spec/model/paths_spec.rb +348 -0
- data/spec/model/relation_spec.rb +255 -0
- data/spec/model/validations_spec.rb +45 -0
- data/spec/model_spec.rb +55 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/extensions/array.rb +6 -0
- data/spec/support/extensions/hash.rb +6 -0
- data/spec/support/macros/her_macros.rb +17 -0
- data/spec/support/macros/model_macros.rb +36 -0
- data/spec/support/macros/request_macros.rb +27 -0
- metadata +201 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Him::JsonApi::Model do
|
|
4
|
+
before do
|
|
5
|
+
Him::API.setup url: "https://api.example.com" do |connection|
|
|
6
|
+
connection.use Him::Middleware::JsonApiParser
|
|
7
|
+
connection.adapter :test do |stub|
|
|
8
|
+
stub.get("/users/1") do
|
|
9
|
+
[
|
|
10
|
+
200,
|
|
11
|
+
{},
|
|
12
|
+
{
|
|
13
|
+
data: {
|
|
14
|
+
id: 1,
|
|
15
|
+
type: "users",
|
|
16
|
+
attributes: {
|
|
17
|
+
name: "Roger Federer"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}.to_json
|
|
22
|
+
]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
stub.get("/users") do
|
|
26
|
+
[
|
|
27
|
+
200,
|
|
28
|
+
{},
|
|
29
|
+
{
|
|
30
|
+
data: [
|
|
31
|
+
{
|
|
32
|
+
id: 1,
|
|
33
|
+
type: "users",
|
|
34
|
+
attributes: {
|
|
35
|
+
name: "Roger Federer"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 2,
|
|
40
|
+
type: "users",
|
|
41
|
+
attributes: {
|
|
42
|
+
name: "Kei Nishikori"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}.to_json
|
|
47
|
+
]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
stub.post("/users", data:
|
|
51
|
+
{
|
|
52
|
+
type: "users",
|
|
53
|
+
attributes: {
|
|
54
|
+
name: "Jeremy Lin"
|
|
55
|
+
}
|
|
56
|
+
}) do
|
|
57
|
+
[
|
|
58
|
+
201,
|
|
59
|
+
{},
|
|
60
|
+
{
|
|
61
|
+
data: {
|
|
62
|
+
id: 3,
|
|
63
|
+
type: "users",
|
|
64
|
+
attributes: {
|
|
65
|
+
name: "Jeremy Lin"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
}.to_json
|
|
70
|
+
]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
stub.patch("/users/1", data:
|
|
74
|
+
{
|
|
75
|
+
type: "users",
|
|
76
|
+
id: 1,
|
|
77
|
+
attributes: {
|
|
78
|
+
name: "Fed GOAT"
|
|
79
|
+
}
|
|
80
|
+
}) do
|
|
81
|
+
[
|
|
82
|
+
200,
|
|
83
|
+
{},
|
|
84
|
+
{
|
|
85
|
+
data: {
|
|
86
|
+
id: 1,
|
|
87
|
+
type: "users",
|
|
88
|
+
attributes: {
|
|
89
|
+
name: "Fed GOAT"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
}.to_json
|
|
94
|
+
]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
stub.delete("/users/1") do
|
|
98
|
+
[204, {}, {}]
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
spawn_model("Foo::User", type: Him::JsonApi::Model)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "allows configuration of type" do
|
|
107
|
+
spawn_model("Foo::Bar", type: Him::JsonApi::Model) do
|
|
108
|
+
type :foobars
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
expect(Foo::Bar.instance_variable_get("@type")).to eql("foobars")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "finds models by id" do
|
|
115
|
+
user = Foo::User.find(1)
|
|
116
|
+
expect(user.attributes).to eql(
|
|
117
|
+
"id" => 1,
|
|
118
|
+
"name" => "Roger Federer"
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "finds a collection of models" do
|
|
123
|
+
users = Foo::User.all
|
|
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
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it "creates a Foo::User" do
|
|
139
|
+
user = Foo::User.new(name: "Jeremy Lin")
|
|
140
|
+
user.save
|
|
141
|
+
expect(user.attributes).to eql(
|
|
142
|
+
"id" => 3,
|
|
143
|
+
"name" => "Jeremy Lin"
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "updates a Foo::User" do
|
|
148
|
+
user = Foo::User.find(1)
|
|
149
|
+
user.name = "Fed GOAT"
|
|
150
|
+
user.save
|
|
151
|
+
expect(user.attributes).to eql(
|
|
152
|
+
"id" => 1,
|
|
153
|
+
"name" => "Fed GOAT"
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "destroys a Foo::User" do
|
|
158
|
+
user = Foo::User.find(1)
|
|
159
|
+
expect(user.destroy).to be_destroyed
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context "undefined methods" do
|
|
163
|
+
it "removes methods that are not compatible with json api" do
|
|
164
|
+
[:parse_root_in_json, :include_root_in_json, :root_element, :primary_key].each do |method|
|
|
165
|
+
expect { Foo::User.new.send(method, :foo) }.to raise_error NoMethodError, "Him::JsonApi::Model does not support the #{method} configuration option"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context "compound document" do
|
|
171
|
+
before do
|
|
172
|
+
Him::API.setup url: "https://api.example.com" do |connection|
|
|
173
|
+
connection.use Him::Middleware::JsonApiParser
|
|
174
|
+
connection.adapter :test do |stub|
|
|
175
|
+
stub.get("/players") do
|
|
176
|
+
[
|
|
177
|
+
200,
|
|
178
|
+
{},
|
|
179
|
+
{
|
|
180
|
+
data: [
|
|
181
|
+
{
|
|
182
|
+
id: 1,
|
|
183
|
+
type: "players",
|
|
184
|
+
attributes: { name: "Roger Federer" },
|
|
185
|
+
relationships: {
|
|
186
|
+
sponsors: {
|
|
187
|
+
data: [
|
|
188
|
+
{ type: "sponsors", id: 1 },
|
|
189
|
+
{ type: "sponsors", id: 2 }
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
racquet: {
|
|
193
|
+
data: { type: "racquets", id: 1 }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: 2,
|
|
199
|
+
type: "players",
|
|
200
|
+
attributes: { name: "Kei Nishikori" },
|
|
201
|
+
relationships: {
|
|
202
|
+
sponsors: {
|
|
203
|
+
data: [
|
|
204
|
+
{ type: "sponsors", id: 2 },
|
|
205
|
+
{ type: "sponsors", id: 3 }
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
racquet: {
|
|
209
|
+
data: { type: "racquets", id: 2 }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 3,
|
|
215
|
+
type: "players",
|
|
216
|
+
attributes: { name: "Hubert Huang", racquet_id: nil },
|
|
217
|
+
relationships: {}
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
included: [
|
|
221
|
+
{ type: "sponsors", id: 1, attributes: { company: "Nike" } },
|
|
222
|
+
{ type: "sponsors", id: 2, attributes: { company: "Wilson" } },
|
|
223
|
+
{ type: "sponsors", id: 3, attributes: { company: "Uniqlo" } },
|
|
224
|
+
{ type: "racquets", id: 1, attributes: { name: "Wilson Pro Staff" } },
|
|
225
|
+
{ type: "racquets", id: 2, attributes: { name: "Wilson Steam" } }
|
|
226
|
+
]
|
|
227
|
+
}.to_json
|
|
228
|
+
]
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
stub.get("/players/3/sponsors") do
|
|
232
|
+
[200, {}, { data: [] }.to_json]
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
spawn_model("Foo::Sponsor", type: Him::JsonApi::Model)
|
|
238
|
+
spawn_model("Foo::Racquet", type: Him::JsonApi::Model)
|
|
239
|
+
spawn_model("Foo::Player", type: Him::JsonApi::Model) do
|
|
240
|
+
has_many :sponsors
|
|
241
|
+
belongs_to :racquet
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it "parses included resources into associations from compound documents" do
|
|
246
|
+
players = Foo::Player.all.to_a
|
|
247
|
+
fed = players.detect { |p| p.name == "Roger Federer" }
|
|
248
|
+
expect(fed.sponsors.map(&:company)).to match_array ["Nike", "Wilson"]
|
|
249
|
+
expect(fed.racquet.name).to eq "Wilson Pro Staff"
|
|
250
|
+
|
|
251
|
+
kei = players.detect { |p| p.name == "Kei Nishikori" }
|
|
252
|
+
expect(kei.sponsors.map(&:company)).to match_array ["Uniqlo", "Wilson"]
|
|
253
|
+
expect(kei.racquet.name).to eq "Wilson Steam"
|
|
254
|
+
|
|
255
|
+
hubert = players.detect { |p| p.name == "Hubert Huang" }
|
|
256
|
+
expect(hubert.sponsors).to eq []
|
|
257
|
+
expect(hubert.racquet).to be_nil
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Him::Middleware::FirstLevelParseJSON do
|
|
6
|
+
subject { described_class.new }
|
|
7
|
+
let(:body_without_errors) { "{\"id\": 1, \"name\": \"Tobias Fünke\", \"metadata\": 3}" }
|
|
8
|
+
let(:body_with_errors) { "{\"id\": 1, \"name\": \"Tobias Fünke\", \"errors\": { \"name\": [ \"not_valid\", \"should_be_present\" ] }, \"metadata\": 3}" }
|
|
9
|
+
let(:body_with_malformed_json) { "wut." }
|
|
10
|
+
let(:body_with_invalid_json) { "true" }
|
|
11
|
+
let(:empty_body) { "" }
|
|
12
|
+
let(:nil_body) { nil }
|
|
13
|
+
|
|
14
|
+
it "parses body as json" do
|
|
15
|
+
subject.parse(body_without_errors).tap do |json|
|
|
16
|
+
expect(json[:data]).to eq(id: 1, name: "Tobias Fünke")
|
|
17
|
+
expect(json[:metadata]).to eq(3)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "parses :body key as json in the env hash" do
|
|
22
|
+
env = { body: body_without_errors }
|
|
23
|
+
subject.on_complete(env)
|
|
24
|
+
env[:body].tap do |json|
|
|
25
|
+
expect(json[:data]).to eq(id: 1, name: "Tobias Fünke")
|
|
26
|
+
expect(json[:metadata]).to eq(3)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "ensures the errors are a hash if there are no errors" do
|
|
31
|
+
expect(subject.parse(body_without_errors)[:errors]).to eq({})
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "ensures the errors are a hash if there are no errors" do
|
|
35
|
+
expect(subject.parse(body_with_errors)[:errors]).to eq(name: %w[not_valid should_be_present])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "ensures that malformed JSON throws an exception" do
|
|
39
|
+
expect { subject.parse(body_with_malformed_json) }.to raise_error(Him::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "wut.")')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "ensures that invalid JSON throws an exception" do
|
|
43
|
+
expect { subject.parse(body_with_invalid_json) }.to raise_error(Him::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "true")')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "ensures that a nil response returns an empty hash" do
|
|
47
|
+
expect(subject.parse(nil_body)[:data]).to eq({})
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "ensures that an empty response returns an empty hash" do
|
|
51
|
+
expect(subject.parse(empty_body)[:data]).to eq({})
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "with status code 204" do
|
|
55
|
+
it "returns an empty body" do
|
|
56
|
+
env = { status: 204 }
|
|
57
|
+
subject.on_complete(env)
|
|
58
|
+
env[:body].tap do |json|
|
|
59
|
+
expect(json[:data]).to eq({})
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Him::Middleware::JsonApiParser do
|
|
6
|
+
subject { described_class.new }
|
|
7
|
+
|
|
8
|
+
context "with valid JSON body" do
|
|
9
|
+
let(:body) { '{"data": {"type": "foo", "id": "bar", "attributes": {"baz": "qux"} }, "meta": {"api": "json api"} }' }
|
|
10
|
+
let(:env) { { body: body } }
|
|
11
|
+
|
|
12
|
+
it "parses body as json" do
|
|
13
|
+
subject.on_complete(env)
|
|
14
|
+
env.fetch(:body).tap do |json|
|
|
15
|
+
expect(json[:data]).to eql(
|
|
16
|
+
type: "foo",
|
|
17
|
+
id: "bar",
|
|
18
|
+
attributes: { baz: "qux" }
|
|
19
|
+
)
|
|
20
|
+
expect(json[:errors]).to eql([])
|
|
21
|
+
expect(json[:metadata]).to eql(api: "json api")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "with status code 204" do
|
|
27
|
+
it "returns an empty body" do
|
|
28
|
+
env = { status: 204 }
|
|
29
|
+
subject.on_complete(env)
|
|
30
|
+
env[:body].tap do |json|
|
|
31
|
+
expect(json[:data]).to eq({})
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'with status code 304' do
|
|
37
|
+
it 'returns an empty body' do
|
|
38
|
+
env = { :status => 304 }
|
|
39
|
+
subject.on_complete(env)
|
|
40
|
+
env[:body].tap do |json|
|
|
41
|
+
expect(json[:data]).to eq({})
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# context "with invalid JSON body" do
|
|
47
|
+
# let(:body) { '"foo"' }
|
|
48
|
+
# it 'ensures that invalid JSON throws an exception' do
|
|
49
|
+
# expect { subject.parse(body) }.to raise_error(Him::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "\"foo\"")')
|
|
50
|
+
# end
|
|
51
|
+
# end
|
|
52
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Him::Middleware::SecondLevelParseJSON do
|
|
6
|
+
subject { described_class.new }
|
|
7
|
+
|
|
8
|
+
context "with valid JSON body" do
|
|
9
|
+
let(:body) { "{\"data\": 1, \"errors\": 2, \"metadata\": 3}" }
|
|
10
|
+
it "parses body as json" do
|
|
11
|
+
subject.parse(body).tap do |json|
|
|
12
|
+
expect(json[:data]).to eq(1)
|
|
13
|
+
expect(json[:errors]).to eq(2)
|
|
14
|
+
expect(json[:metadata]).to eq(3)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "parses :body key as json in the env hash" do
|
|
19
|
+
env = { body: body }
|
|
20
|
+
subject.on_complete(env)
|
|
21
|
+
env[:body].tap do |json|
|
|
22
|
+
expect(json[:data]).to eq(1)
|
|
23
|
+
expect(json[:errors]).to eq(2)
|
|
24
|
+
expect(json[:metadata]).to eq(3)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context "with invalid JSON body" do
|
|
30
|
+
let(:body) { '"foo"' }
|
|
31
|
+
it "ensures that invalid JSON throws an exception" do
|
|
32
|
+
expect { subject.parse(body) }.to raise_error(Him::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "\"foo\"")')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe Him::Model::Associations::AssociationProxy do
|
|
6
|
+
describe "proxy assignment methods" do
|
|
7
|
+
before do
|
|
8
|
+
Him::API.setup url: "https://api.example.com" do |builder|
|
|
9
|
+
builder.use Him::Middleware::FirstLevelParseJSON
|
|
10
|
+
builder.use Faraday::Request::UrlEncoded
|
|
11
|
+
builder.adapter :test do |stub|
|
|
12
|
+
stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias Fünke" }.to_json] }
|
|
13
|
+
stub.get("/users/1/fish") { [200, {}, { id: 1, name: "Tobias's Fish" }.to_json] }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
spawn_model "User" do
|
|
17
|
+
has_one :fish
|
|
18
|
+
end
|
|
19
|
+
spawn_model "Fish"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
subject { User.find(1) }
|
|
23
|
+
|
|
24
|
+
it "should assign value" do
|
|
25
|
+
subject.fish.name = "Fishy"
|
|
26
|
+
expect(subject.fish.name).to eq "Fishy"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|