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,187 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require File.join(File.dirname(__FILE__), "../spec_helper.rb")
|
|
4
|
+
|
|
5
|
+
describe Him::Model::HTTP do
|
|
6
|
+
context "binding a model with an API" do
|
|
7
|
+
let(:api1) { Him::API.new url: "https://api1.example.com" }
|
|
8
|
+
let(:api2) { Him::API.new url: "https://api2.example.com" }
|
|
9
|
+
|
|
10
|
+
before do
|
|
11
|
+
spawn_model("Foo::User")
|
|
12
|
+
spawn_model("Foo::Comment")
|
|
13
|
+
Him::API.setup url: "https://api.example.com"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context "when binding a model to its superclass' her_api" do
|
|
17
|
+
before do
|
|
18
|
+
spawn_model "Foo::Superclass"
|
|
19
|
+
Foo::Superclass.uses_api api1
|
|
20
|
+
Foo::Subclass = Class.new(Foo::Superclass)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
specify { expect(Foo::Subclass.her_api).to eq(Foo::Superclass.her_api) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "when changing her_api without changing the parent class' her_api" do
|
|
27
|
+
before do
|
|
28
|
+
spawn_model "Foo::Superclass"
|
|
29
|
+
Foo::Subclass = Class.new(Foo::Superclass)
|
|
30
|
+
Foo::Superclass.uses_api api1
|
|
31
|
+
Foo::Subclass.uses_api api2
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
specify { expect(Foo::Subclass.her_api).not_to eq(Foo::Superclass.her_api) }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context "making HTTP requests" do
|
|
39
|
+
before do
|
|
40
|
+
Him::API.setup url: "https://api.example.com" do |builder|
|
|
41
|
+
builder.use Him::Middleware::FirstLevelParseJSON
|
|
42
|
+
builder.use Faraday::Request::UrlEncoded
|
|
43
|
+
builder.adapter :test do |stub|
|
|
44
|
+
stub.get("/users") { [200, {}, [{ id: 1 }].to_json] }
|
|
45
|
+
stub.get("/users/1") { [200, {}, { id: 1 }.to_json] }
|
|
46
|
+
stub.get("/users/popular") do |env|
|
|
47
|
+
if env[:params]["page"] == "2"
|
|
48
|
+
[200, {}, [{ id: 3 }, { id: 4 }].to_json]
|
|
49
|
+
else
|
|
50
|
+
[200, {}, [{ id: 1 }, { id: 2 }].to_json]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
spawn_model "Foo::User"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe :get do
|
|
60
|
+
subject { Foo::User.get(:popular) }
|
|
61
|
+
|
|
62
|
+
describe "#length" do
|
|
63
|
+
subject { super().length }
|
|
64
|
+
it { is_expected.to eq(2) }
|
|
65
|
+
end
|
|
66
|
+
specify { expect(subject.first.id).to eq(1) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe :get_raw do
|
|
70
|
+
context "with a block" do
|
|
71
|
+
specify do
|
|
72
|
+
Foo::User.get_raw("/users") do |parsed_data, _response|
|
|
73
|
+
expect(parsed_data[:data]).to eq([{ id: 1 }])
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context "with a return value" do
|
|
79
|
+
subject { Foo::User.get_raw("/users") }
|
|
80
|
+
specify { expect(subject[:parsed_data][:data]).to eq([{ id: 1 }]) }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe :get_collection do
|
|
85
|
+
context "with a String path" do
|
|
86
|
+
subject { Foo::User.get_collection("/users/popular") }
|
|
87
|
+
|
|
88
|
+
describe "#length" do
|
|
89
|
+
subject { super().length }
|
|
90
|
+
it { is_expected.to eq(2) }
|
|
91
|
+
end
|
|
92
|
+
specify { expect(subject.first.id).to eq(1) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context "with a Symbol" do
|
|
96
|
+
subject { Foo::User.get_collection(:popular) }
|
|
97
|
+
|
|
98
|
+
describe "#length" do
|
|
99
|
+
subject { super().length }
|
|
100
|
+
it { is_expected.to eq(2) }
|
|
101
|
+
end
|
|
102
|
+
specify { expect(subject.first.id).to eq(1) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context "with extra parameters" do
|
|
106
|
+
subject { Foo::User.get_collection(:popular, page: 2) }
|
|
107
|
+
|
|
108
|
+
describe "#length" do
|
|
109
|
+
subject { super().length }
|
|
110
|
+
it { is_expected.to eq(2) }
|
|
111
|
+
end
|
|
112
|
+
specify { expect(subject.first.id).to eq(3) }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe :get_resource do
|
|
117
|
+
context "with a String path" do
|
|
118
|
+
subject { Foo::User.get_resource("/users/1") }
|
|
119
|
+
|
|
120
|
+
describe "#id" do
|
|
121
|
+
subject { super().id }
|
|
122
|
+
it { is_expected.to eq(1) }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
context "with a Symbol" do
|
|
127
|
+
subject { Foo::User.get_resource(:"1") }
|
|
128
|
+
|
|
129
|
+
describe "#id" do
|
|
130
|
+
subject { super().id }
|
|
131
|
+
it { is_expected.to eq(1) }
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe :get_raw do
|
|
137
|
+
specify do
|
|
138
|
+
Foo::User.get_raw(:popular) do |parsed_data, _response|
|
|
139
|
+
expect(parsed_data[:data]).to eq([{ id: 1 }, { id: 2 }])
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context "setting custom HTTP requests" do
|
|
146
|
+
before do
|
|
147
|
+
Him::API.setup url: "https://api.example.com" do |connection|
|
|
148
|
+
connection.use Him::Middleware::FirstLevelParseJSON
|
|
149
|
+
connection.adapter :test do |stub|
|
|
150
|
+
stub.get("/users/popular") { [200, {}, [{ id: 1 }, { id: 2 }].to_json] }
|
|
151
|
+
stub.post("/users/from_default") { [200, {}, { id: 4 }.to_json] }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
spawn_model "Foo::User"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
subject { Foo::User }
|
|
159
|
+
|
|
160
|
+
describe :custom_get do
|
|
161
|
+
before do
|
|
162
|
+
Foo::User.custom_get :popular, :recent
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it { is_expected.to respond_to(:popular) }
|
|
166
|
+
it { is_expected.to respond_to(:recent) }
|
|
167
|
+
|
|
168
|
+
it "makes HTTP request" do
|
|
169
|
+
expect(Foo::User.popular.length).to be 2
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe :custom_post do
|
|
174
|
+
before do
|
|
175
|
+
Foo::User.custom_post :from_default
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it { is_expected.to respond_to(:from_default) }
|
|
179
|
+
|
|
180
|
+
it "makes HTTP request" do
|
|
181
|
+
user = Foo::User.from_default(name: "Tobias Fünke")
|
|
182
|
+
expect(user.id).to be 4
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require File.join(File.dirname(__FILE__), "../spec_helper.rb")
|
|
4
|
+
|
|
5
|
+
describe Him::Model::Introspection do
|
|
6
|
+
context "introspecting a resource" 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.post("/users") { [200, {}, { id: 1, name: "Tobias Funke" }.to_json] }
|
|
13
|
+
stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias Funke" }.to_json] }
|
|
14
|
+
stub.put("/users/1") { [200, {}, { id: 1, name: "Tobias Funke" }.to_json] }
|
|
15
|
+
stub.delete("/users/1") { [200, {}, { id: 1, name: "Tobias Funke" }.to_json] }
|
|
16
|
+
stub.get("/projects/1/comments") { [200, {}, [{ id: 1, body: "Hello!" }].to_json] }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
spawn_model "Foo::User"
|
|
21
|
+
spawn_model "Foo::Comment" do
|
|
22
|
+
collection_path "projects/:project_id/comments"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe "#inspect" do
|
|
27
|
+
it "outputs resource attributes for an existing resource" do
|
|
28
|
+
@user = Foo::User.find(1)
|
|
29
|
+
expect(["#<Foo::User(users/1) name=\"Tobias Funke\" id=1>", "#<Foo::User(users/1) id=1 name=\"Tobias Funke\">"]).to include(@user.inspect)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "outputs resource attributes for an not-saved-yet resource" do
|
|
33
|
+
@user = Foo::User.new(name: "Tobias Funke")
|
|
34
|
+
expect(@user.inspect).to eq("#<Foo::User(users) name=\"Tobias Funke\">")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "outputs resource attributes using getters" do
|
|
38
|
+
@user = Foo::User.new(name: "Tobias Funke", password: "Funke")
|
|
39
|
+
@user.instance_eval do
|
|
40
|
+
def password
|
|
41
|
+
"filtered"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
expect(@user.inspect).to include("name=\"Tobias Funke\"")
|
|
45
|
+
expect(@user.inspect).to include("password=\"filtered\"")
|
|
46
|
+
expect(@user.inspect).not_to include("password=\"Funke\"")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "support dash on attribute" do
|
|
50
|
+
@user = Foo::User.new(:'life-span' => "3 years")
|
|
51
|
+
expect(@user.inspect).to include("life-span=\"3 years\"")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe "#inspect with cyclic associations" do
|
|
56
|
+
before do
|
|
57
|
+
Him::API.setup url: "https://api.example.com" do |builder|
|
|
58
|
+
builder.use Him::Middleware::FirstLevelParseJSON
|
|
59
|
+
builder.adapter :test do |stub|
|
|
60
|
+
stub.get("/users/1") { [200, {}, { id: 1, name: "Tobias", comments: [{ id: 2, body: "Hey!", user_id: 1 }] }.to_json] }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
spawn_model "Foo::User" do
|
|
65
|
+
has_many :comments, class_name: "Foo::Comment"
|
|
66
|
+
end
|
|
67
|
+
spawn_model "Foo::Comment" do
|
|
68
|
+
belongs_to :user, class_name: "Foo::User"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "does not infinitely recurse" do
|
|
73
|
+
user = Foo::User.find(1)
|
|
74
|
+
expect { Timeout.timeout(5) { user.inspect } }.not_to raise_error
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "truncates cyclic references with ..." do
|
|
78
|
+
user = Foo::User.find(1)
|
|
79
|
+
output = user.inspect
|
|
80
|
+
expect(output).to include("Foo::User")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe "#inspect with errors in resource path" do
|
|
85
|
+
it "prints the resource path as “unknown”" do
|
|
86
|
+
@comment = Foo::Comment.where(project_id: 1).first
|
|
87
|
+
path = "<unknown path, missing `project_id`>"
|
|
88
|
+
expect(["#<Foo::Comment(#{path}) body=\"Hello!\" id=1>", "#<Foo::Comment(#{path}) id=1 body=\"Hello!\">"]).to include(@comment.inspect)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe "#her_nearby_class" do
|
|
94
|
+
context "for a class inside of a module" do
|
|
95
|
+
before do
|
|
96
|
+
spawn_model "Foo::User"
|
|
97
|
+
spawn_model "Foo::AccessRecord"
|
|
98
|
+
spawn_model "AccessRecord"
|
|
99
|
+
spawn_model "Log"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "returns a sibling class, if found" do
|
|
103
|
+
expect(Foo::User.her_nearby_class("AccessRecord")).to eq(Foo::AccessRecord)
|
|
104
|
+
expect(AccessRecord.her_nearby_class("Log")).to eq(Log)
|
|
105
|
+
expect(Foo::User.her_nearby_class("Log")).to eq(Log)
|
|
106
|
+
expect { Foo::User.her_nearby_class("X") }.to raise_error(NameError)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require File.join(File.dirname(__FILE__), "../spec_helper.rb")
|
|
4
|
+
|
|
5
|
+
describe Him::Model::NestedAttributes do
|
|
6
|
+
context "with a belongs_to association" 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
|
+
end
|
|
12
|
+
|
|
13
|
+
spawn_model "Foo::User" do
|
|
14
|
+
belongs_to :company, path: "/organizations/:id", foreign_key: :organization_id
|
|
15
|
+
accepts_nested_attributes_for :company
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
spawn_model "Foo::Company"
|
|
19
|
+
|
|
20
|
+
@user_with_data_through_nested_attributes = Foo::User.new name: "Test", company_attributes: { name: "Example Company" }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context "when child does not yet exist" do
|
|
24
|
+
it "creates an instance of the associated class" do
|
|
25
|
+
expect(@user_with_data_through_nested_attributes.company).to be_a(Foo::Company)
|
|
26
|
+
expect(@user_with_data_through_nested_attributes.company.name).to eq("Example Company")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context "when child does exist" do
|
|
31
|
+
it "updates the attributes of the associated object" do
|
|
32
|
+
@user_with_data_through_nested_attributes.company_attributes = { name: "Fünke's Company" }
|
|
33
|
+
expect(@user_with_data_through_nested_attributes.company).to be_a(Foo::Company)
|
|
34
|
+
expect(@user_with_data_through_nested_attributes.company.name).to eq("Fünke's Company")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context "with a has_one association" do
|
|
40
|
+
before do
|
|
41
|
+
Him::API.setup url: "https://api.example.com" do |builder|
|
|
42
|
+
builder.use Him::Middleware::FirstLevelParseJSON
|
|
43
|
+
builder.use Faraday::Request::UrlEncoded
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
spawn_model "Foo::User" do
|
|
47
|
+
has_one :pet
|
|
48
|
+
accepts_nested_attributes_for :pet
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
spawn_model "Foo::Pet"
|
|
52
|
+
|
|
53
|
+
@user_with_data_through_nested_attributes = Foo::User.new name: "Test", pet_attributes: { name: "Hasi" }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context "when child does not yet exist" do
|
|
57
|
+
it "creates an instance of the associated class" do
|
|
58
|
+
expect(@user_with_data_through_nested_attributes.pet).to be_a(Foo::Pet)
|
|
59
|
+
expect(@user_with_data_through_nested_attributes.pet.name).to eq("Hasi")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context "when child does exist" do
|
|
64
|
+
it "updates the attributes of the associated object" do
|
|
65
|
+
@user_with_data_through_nested_attributes.pet_attributes = { name: "Rodriguez" }
|
|
66
|
+
expect(@user_with_data_through_nested_attributes.pet).to be_a(Foo::Pet)
|
|
67
|
+
expect(@user_with_data_through_nested_attributes.pet.name).to eq("Rodriguez")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context "with a has_many association" do
|
|
73
|
+
before do
|
|
74
|
+
Him::API.setup url: "https://api.example.com" do |builder|
|
|
75
|
+
builder.use Him::Middleware::FirstLevelParseJSON
|
|
76
|
+
builder.use Faraday::Request::UrlEncoded
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
spawn_model "Foo::User" do
|
|
80
|
+
has_many :pets
|
|
81
|
+
accepts_nested_attributes_for :pets
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
spawn_model "Foo::Pet"
|
|
85
|
+
|
|
86
|
+
@user_with_data_through_nested_attributes = Foo::User.new name: "Test", pets_attributes: [{ name: "Hasi" }, { name: "Rodriguez" }]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context "when children do not yet exist" do
|
|
90
|
+
it "creates an instance of the associated class" do
|
|
91
|
+
expect(@user_with_data_through_nested_attributes.pets.length).to eq(2)
|
|
92
|
+
expect(@user_with_data_through_nested_attributes.pets[0]).to be_a(Foo::Pet)
|
|
93
|
+
expect(@user_with_data_through_nested_attributes.pets[1]).to be_a(Foo::Pet)
|
|
94
|
+
expect(@user_with_data_through_nested_attributes.pets[0].name).to eq("Hasi")
|
|
95
|
+
expect(@user_with_data_through_nested_attributes.pets[1].name).to eq("Rodriguez")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context "with a has_many association as a Hash" do
|
|
101
|
+
before do
|
|
102
|
+
Him::API.setup url: "https://api.example.com" do |builder|
|
|
103
|
+
builder.use Him::Middleware::FirstLevelParseJSON
|
|
104
|
+
builder.use Faraday::Request::UrlEncoded
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
spawn_model "Foo::User" do
|
|
108
|
+
has_many :pets
|
|
109
|
+
accepts_nested_attributes_for :pets
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
spawn_model "Foo::Pet"
|
|
113
|
+
|
|
114
|
+
@user_with_data_through_nested_attributes_as_hash = Foo::User.new name: "Test", pets_attributes: { "0" => { name: "Hasi" }, "1" => { name: "Rodriguez" } }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context "when children do not yet exist" do
|
|
118
|
+
it "creates an instance of the associated class" do
|
|
119
|
+
expect(@user_with_data_through_nested_attributes_as_hash.pets.length).to eq(2)
|
|
120
|
+
expect(@user_with_data_through_nested_attributes_as_hash.pets[0]).to be_a(Foo::Pet)
|
|
121
|
+
expect(@user_with_data_through_nested_attributes_as_hash.pets[1]).to be_a(Foo::Pet)
|
|
122
|
+
expect(@user_with_data_through_nested_attributes_as_hash.pets[0].name).to eq("Hasi")
|
|
123
|
+
expect(@user_with_data_through_nested_attributes_as_hash.pets[1].name).to eq("Rodriguez")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
context "with an unknown association" do
|
|
129
|
+
it "raises an error" do
|
|
130
|
+
expect do
|
|
131
|
+
spawn_model("Foo::User") { accepts_nested_attributes_for :company }
|
|
132
|
+
end.to raise_error(Him::Errors::AssociationUnknownError, "Unknown association name :company")
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|