csv_model 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/.gitignore +34 -0
- data/.travis.yml +9 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +31 -0
- data/LICENSE +21 -0
- data/README.md +6 -0
- data/Rakefile +20 -0
- data/csv_model.gemspec +24 -0
- data/lib/csv_model/column.rb +27 -0
- data/lib/csv_model/errors.rb +5 -0
- data/lib/csv_model/extensions.rb +45 -0
- data/lib/csv_model/header_row.rb +133 -0
- data/lib/csv_model/model.rb +96 -0
- data/lib/csv_model/object_with_status_snapshot.rb +112 -0
- data/lib/csv_model/record_status.rb +16 -0
- data/lib/csv_model/row.rb +128 -0
- data/lib/csv_model/utilities.rb +15 -0
- data/lib/csv_model/version.rb +3 -0
- data/lib/csv_model.rb +17 -0
- data/spec/csv_model/column_spec.rb +31 -0
- data/spec/csv_model/header_row_spec.rb +278 -0
- data/spec/csv_model/model_spec.rb +192 -0
- data/spec/csv_model/object_with_status_snapshot_spec.rb +297 -0
- data/spec/csv_model/row_spec.rb +351 -0
- data/spec/csv_model/utilities_spec.rb +82 -0
- data/spec/spec_helper.rb +20 -0
- metadata +119 -0
@@ -0,0 +1,297 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CSVModel::ObjectWithStatusSnapshot do
|
4
|
+
let(:model) { double("model", changed?: false, marked_for_destruction?: false, new_record?: false, valid?: false) }
|
5
|
+
let(:subject) { described_class.new(model) }
|
6
|
+
|
7
|
+
describe "#assign_attributes" do
|
8
|
+
it "does not raise when model is nil" do
|
9
|
+
subject = described_class.new(nil)
|
10
|
+
expect { subject.assign_attributes({}) }.to_not raise_exception
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#errors" do
|
15
|
+
it "does not raise when model is nil" do
|
16
|
+
subject = described_class.new(nil)
|
17
|
+
expect { subject.errors }.to_not raise_exception
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns an error when model is nil" do
|
21
|
+
subject = described_class.new(nil)
|
22
|
+
expect(subject.errors).to eq(["Record could not be created or updated"])
|
23
|
+
end
|
24
|
+
|
25
|
+
it "delegates to errors when model is not nil" do
|
26
|
+
model = double("model", errors: :some_errors)
|
27
|
+
subject = described_class.new(model)
|
28
|
+
expect(subject.errors).to eq(:some_errors)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "uses to errors#full_messages when available" do
|
32
|
+
errors = double(full_messages: :some_errors)
|
33
|
+
model = double("model", errors: errors)
|
34
|
+
subject = described_class.new(model)
|
35
|
+
expect(subject.errors).to eq(:some_errors)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#mark_as_duplicate" do
|
40
|
+
describe "internals" do
|
41
|
+
it "sets is_duplicate flag to true" do
|
42
|
+
subject.mark_as_duplicate
|
43
|
+
expect(subject.instance_variable_get("@is_duplicate")).to eq(true)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#save" do
|
49
|
+
context "with an invalid model" do
|
50
|
+
let(:model) { double("model", changed?: true, marked_for_destruction?: false, new_record?: true, valid?: false) }
|
51
|
+
|
52
|
+
it "does not calls save on underlying model" do
|
53
|
+
expect(model).to_not receive(:save)
|
54
|
+
subject.save
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "on a normal-run with an editable, valid model" do
|
59
|
+
let(:model) { double("model", changed?: true, marked_for_destruction?: false, new_record?: false, valid?: true) }
|
60
|
+
|
61
|
+
it "calls save on underlying model" do
|
62
|
+
expect(model).to receive(:save)
|
63
|
+
subject.save
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "on a normal-run with an editable, valid model that is marked for destruction" do
|
68
|
+
let(:model) { double("model", changed?: true, marked_for_destruction?: true, new_record?: false, valid?: true) }
|
69
|
+
|
70
|
+
it "calls destroy on underlying model" do
|
71
|
+
expect(model).to receive(:destroy)
|
72
|
+
subject.save
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "internals" do
|
77
|
+
before do
|
78
|
+
allow(model).to receive(:valid?).and_return(true)
|
79
|
+
allow(model).to receive(:save).and_return(true)
|
80
|
+
subject.save
|
81
|
+
end
|
82
|
+
|
83
|
+
it "sets was_saved flag to true when editable and valid" do
|
84
|
+
allow(model).to receive(:valid?).and_return(true)
|
85
|
+
subject.save(dry_run: true)
|
86
|
+
expect(subject.instance_variable_get("@was_saved")).to eq(true)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "sets was_saved flag to false when not editable" do
|
90
|
+
allow(model).to receive(:editable?).and_return(false)
|
91
|
+
allow(model).to receive(:valid?).and_return(true)
|
92
|
+
subject.save(dry_run: true)
|
93
|
+
expect(subject.instance_variable_get("@was_saved")).to eq(false)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "sets was_saved flag to false when not valid" do
|
97
|
+
allow(model).to receive(:valid?).and_return(false)
|
98
|
+
subject.save(dry_run: true)
|
99
|
+
expect(subject.instance_variable_get("@was_saved")).to eq(false)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "capture dry run flag to false" do
|
103
|
+
expect(subject.instance_variable_get("@is_dry_run")).to eq(false)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "captures changed flag" do
|
107
|
+
expect(subject.instance_variable_get("@was_changed")).to eq(false)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "captures deleted flag" do
|
111
|
+
expect(subject.instance_variable_get("@was_deleted")).to eq(false)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "captures editable flag" do
|
115
|
+
expect(subject.instance_variable_get("@was_editable")).to eq(true)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "captures new flag" do
|
119
|
+
expect(subject.instance_variable_get("@was_new")).to eq(false)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "captures save flag" do
|
123
|
+
expect(subject.instance_variable_get("@was_saved")).to eq(true)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "captures valid flag" do
|
127
|
+
expect(subject.instance_variable_get("@was_valid")).to eq(true)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "#status" do
|
133
|
+
it "is ERROR_ON_READ when model is nil" do
|
134
|
+
subject = described_class.new(nil)
|
135
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_READ)
|
136
|
+
end
|
137
|
+
|
138
|
+
context "during a dry-run" do
|
139
|
+
it "is DUPLICATE when record is marked as a duplicate" do
|
140
|
+
subject.mark_as_duplicate
|
141
|
+
subject.save(dry_run: true)
|
142
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::DUPLICATE)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "is NOT_CHANGED when record exists and was not changed" do
|
146
|
+
subject.save(dry_run: true)
|
147
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::NOT_CHANGED)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "is CREATE when record was new and valid" do
|
151
|
+
expect(model).to receive(:new_record?).and_return(true)
|
152
|
+
expect(model).to receive(:valid?).and_return(true)
|
153
|
+
subject.save(dry_run: true)
|
154
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::CREATE)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "is ERROR_ON_CREATE when record was new and not valid" do
|
158
|
+
expect(model).to receive(:new_record?).and_return(true)
|
159
|
+
subject.save(dry_run: true)
|
160
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_CREATE)
|
161
|
+
end
|
162
|
+
|
163
|
+
it "is DELETE when record exists and is marked for destruction" do
|
164
|
+
expect(model).to receive(:marked_for_destruction?).and_return(true)
|
165
|
+
subject.save(dry_run: true)
|
166
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::DELETE)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "is ERROR_ON_DELETE when record was new and marked for destruction" do
|
170
|
+
expect(model).to receive(:new_record?).and_return(true)
|
171
|
+
expect(model).to receive(:marked_for_destruction?).and_return(true)
|
172
|
+
subject.save(dry_run: true)
|
173
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_DELETE)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "is UPDATE when record exists, was changed and was valid" do
|
177
|
+
expect(model).to receive(:changed?).and_return(true)
|
178
|
+
expect(model).to receive(:valid?).and_return(true)
|
179
|
+
subject.save(dry_run: true)
|
180
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::UPDATE)
|
181
|
+
end
|
182
|
+
|
183
|
+
it "is ERROR_ON_UPDATE when record exists but was changed and not editable" do
|
184
|
+
expect(model).to receive(:changed?).and_return(true)
|
185
|
+
expect(model).to receive(:editable?).and_return(false)
|
186
|
+
subject.save(dry_run: true)
|
187
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
|
188
|
+
end
|
189
|
+
|
190
|
+
it "is ERROR_ON_UPDATE when record exists, was changed and was not valid" do
|
191
|
+
expect(model).to receive(:changed?).and_return(true)
|
192
|
+
subject.save(dry_run: true)
|
193
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context "during normal processing" do
|
198
|
+
it "is not DUPLICATE when record is marked as a duplicate" do
|
199
|
+
subject.mark_as_duplicate
|
200
|
+
subject.save
|
201
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::NOT_CHANGED)
|
202
|
+
end
|
203
|
+
|
204
|
+
it "is NOT_CHANGED when record exists and is not changed" do
|
205
|
+
subject.save
|
206
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::NOT_CHANGED)
|
207
|
+
end
|
208
|
+
|
209
|
+
it "is CREATE when record is new, valid and saves" do
|
210
|
+
expect(model).to receive(:new_record?).and_return(true)
|
211
|
+
expect(model).to receive(:valid?).and_return(true)
|
212
|
+
expect(model).to receive(:save).and_return(true)
|
213
|
+
subject.save
|
214
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::CREATE)
|
215
|
+
end
|
216
|
+
|
217
|
+
it "is ERROR_ON_CREATE when record is new and not valid" do
|
218
|
+
expect(model).to receive(:new_record?).and_return(true)
|
219
|
+
subject.save
|
220
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_CREATE)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "is DELETE when record exists, is marked for destruction and saves" do
|
224
|
+
expect(model).to receive(:marked_for_destruction?).and_return(true)
|
225
|
+
subject.save
|
226
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::DELETE)
|
227
|
+
end
|
228
|
+
|
229
|
+
it "is ERROR_ON_DELETE when record is new and marked for destruction" do
|
230
|
+
expect(model).to receive(:new_record?).and_return(true)
|
231
|
+
expect(model).to receive(:marked_for_destruction?).and_return(true)
|
232
|
+
subject.save
|
233
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_DELETE)
|
234
|
+
end
|
235
|
+
|
236
|
+
it "is UPDATE when record exists, is changed, valid and saves" do
|
237
|
+
expect(model).to receive(:changed?).and_return(true)
|
238
|
+
expect(model).to receive(:save).and_return(true)
|
239
|
+
expect(model).to receive(:valid?).and_return(true)
|
240
|
+
subject.save
|
241
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::UPDATE)
|
242
|
+
end
|
243
|
+
|
244
|
+
it "is ERROR_ON_UPDATE when record exists, is changed and is not valid" do
|
245
|
+
expect(model).to receive(:changed?).and_return(true)
|
246
|
+
subject.save
|
247
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
|
248
|
+
end
|
249
|
+
|
250
|
+
it "is ERROR_ON_UPDATE when record exists, is changed, is valid and does not save" do
|
251
|
+
expect(model).to receive(:changed?).and_return(true)
|
252
|
+
subject.save
|
253
|
+
expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
describe "#valid?" do
|
259
|
+
it "does not raise when model is nil" do
|
260
|
+
subject = described_class.new(nil)
|
261
|
+
expect { subject.valid? }.to_not raise_exception
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe "internals" do
|
266
|
+
describe "#is_duplicate?" do
|
267
|
+
it "doesn't respond to is_duplicate?" do
|
268
|
+
expect(subject.respond_to?(:is_duplicate?)).to eq(false)
|
269
|
+
end
|
270
|
+
|
271
|
+
it "defaults to nil" do
|
272
|
+
expect(subject.send(:is_duplicate?)).to eq(nil)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "displays @is_duplicate" do
|
276
|
+
subject.instance_variable_set("@is_duplicate", true)
|
277
|
+
expect(subject.send(:is_duplicate?)).to eq(true)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
describe "#was_saved?" do
|
282
|
+
it "doesn't respond to was_saved?" do
|
283
|
+
expect(subject.respond_to?(:was_saved?)).to eq(false)
|
284
|
+
end
|
285
|
+
|
286
|
+
it "defaults to nil" do
|
287
|
+
expect(subject.send(:was_saved?)).to eq(nil)
|
288
|
+
end
|
289
|
+
|
290
|
+
it "displays @was_saved" do
|
291
|
+
subject.instance_variable_set("@was_saved", true)
|
292
|
+
expect(subject.send(:was_saved?)).to eq(true)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
@@ -0,0 +1,351 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CSVModel::Row do
|
4
|
+
|
5
|
+
let(:header) { double("header") }
|
6
|
+
let(:column) { double("column", key: "column one", model_attribute: :column_one, name: "Column One") }
|
7
|
+
let(:columns) { [column] }
|
8
|
+
let(:primary_key_columns) { [] }
|
9
|
+
|
10
|
+
let(:data) { ["Value One"] }
|
11
|
+
let(:subject) { described_class.new(header, data) }
|
12
|
+
|
13
|
+
before do
|
14
|
+
allow(header).to receive(:columns).and_return(columns)
|
15
|
+
allow(header).to receive(:column_index).and_return(nil)
|
16
|
+
allow(header).to receive(:column_index).with("column one").and_return(0)
|
17
|
+
allow(header).to receive(:primary_key_columns).and_return(primary_key_columns)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#errors" do
|
21
|
+
context "when no errors" do
|
22
|
+
let(:model) { double("model") }
|
23
|
+
let(:subject) { described_class.new(header, data, model: model) }
|
24
|
+
let(:model_instance) { double("model-instance", changed?: false, marked_for_destruction?: false, new_record?: false, valid?: true) }
|
25
|
+
|
26
|
+
before do
|
27
|
+
allow(model).to receive(:find_row_model).and_return(model_instance)
|
28
|
+
allow(model_instance).to receive(:save)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns an empty array when no errors" do
|
32
|
+
expect(subject.errors).to eq([])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when subject is a duplicate" do
|
37
|
+
before do
|
38
|
+
subject.mark_as_duplicate
|
39
|
+
end
|
40
|
+
|
41
|
+
context "during normal processing" do
|
42
|
+
it "does not return an error" do
|
43
|
+
expect(subject.errors).to_not include("Duplicate row")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "during a dry-run" do
|
48
|
+
let(:subject) { described_class.new(header, data, dry_run: true) }
|
49
|
+
|
50
|
+
it "returns an error" do
|
51
|
+
expect(subject.errors).to include("Duplicate row")
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when the row has a primary key" do
|
55
|
+
let(:primary_key_columns) { [column] }
|
56
|
+
|
57
|
+
it "returns an error specific to the column" do
|
58
|
+
expect(subject.errors).to include("Duplicate Column One")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "when the model instance has errors" do
|
65
|
+
let(:model) { double("model") }
|
66
|
+
let(:subject) { described_class.new(header, data, model: model) }
|
67
|
+
let(:model_instance) { double("model-instance", changed?: false, errors: errors, marked_for_destruction?: false, new_record?: false, valid?: false) }
|
68
|
+
let(:errors) { ["Message one", "Message two"] }
|
69
|
+
|
70
|
+
before do
|
71
|
+
allow(model).to receive(:find_row_model).and_return(model_instance)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "includes the model instance errors" do
|
75
|
+
expect(subject.errors).to eq(errors)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#index, #[]" do
|
81
|
+
it "returns nil when index is before than first element" do
|
82
|
+
expect(subject.index(-1)).to eq(nil)
|
83
|
+
expect(subject[-1]).to eq(nil)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "returns nil when index is after than the last element" do
|
87
|
+
expect(subject.index(data.size)).to eq(nil)
|
88
|
+
expect(subject[data.size]).to eq(nil)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns the value at the specified index" do
|
92
|
+
expect(subject.index(0)).to eq("Value One")
|
93
|
+
expect(subject[0]).to eq("Value One")
|
94
|
+
end
|
95
|
+
|
96
|
+
it "returns the value for the specified column key" do
|
97
|
+
expect(subject.index("column one")).to eq("Value One")
|
98
|
+
expect(subject["column one"]).to eq("Value One")
|
99
|
+
end
|
100
|
+
|
101
|
+
it "returns nil when the specified column key not exist" do
|
102
|
+
expect(subject.index("column two")).to eq(nil)
|
103
|
+
expect(subject["column two"]).to eq(nil)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#key" do
|
108
|
+
it "returns the entire row if no primary key is defined" do
|
109
|
+
expect(subject.key).to eq(data)
|
110
|
+
end
|
111
|
+
|
112
|
+
context "when the row has a single primary key column" do
|
113
|
+
let(:primary_key_columns) { [column] }
|
114
|
+
|
115
|
+
it "returns the value for the key column" do
|
116
|
+
expect(subject.key).to eq(data.first)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when the row has a composite key" do
|
121
|
+
let(:columns) { [
|
122
|
+
double("column", key: "column one"),
|
123
|
+
double("column", key: "column two"),
|
124
|
+
double("column", key: "column three")
|
125
|
+
] }
|
126
|
+
let(:primary_key_columns) { [
|
127
|
+
double("column", key: "column one"),
|
128
|
+
double("column", key: "column two")
|
129
|
+
] }
|
130
|
+
let(:data) { ["Value One", "Value Two", "Value Three"] }
|
131
|
+
|
132
|
+
before do
|
133
|
+
allow(header).to receive(:column_index).with("column two").and_return(1)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns the value for the key columns" do
|
137
|
+
expect(subject.key).to eq(["Value One", "Value Two"])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "#mark_as_duplicate" do
|
143
|
+
it "returns true" do
|
144
|
+
expect(subject.mark_as_duplicate).to eq(true)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "#marked_as_duplicate?" do
|
149
|
+
it "returns false when not marked as duplicate" do
|
150
|
+
expect(subject.marked_as_duplicate?).to eq(false)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "returns true when marked as duplicate" do
|
154
|
+
subject.mark_as_duplicate
|
155
|
+
expect(subject.marked_as_duplicate?).to eq(true)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "#status" do
|
160
|
+
let(:model) { double("model") }
|
161
|
+
let(:wrapper) { double("wrapper", status: :some_status) }
|
162
|
+
|
163
|
+
before do
|
164
|
+
allow(subject).to receive(:find).and_return(model)
|
165
|
+
allow(wrapper).to receive(:assign_attributes)
|
166
|
+
allow(wrapper).to receive(:save)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "is delegates status to the model" do
|
170
|
+
expect(CSVModel::ObjectWithStatusSnapshot).to receive(:new).and_return(wrapper)
|
171
|
+
expect(subject.status).to eq(:some_status)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "#valid?" do
|
176
|
+
it "returns true if no errors" do
|
177
|
+
expect(subject).to receive(:errors).and_return([])
|
178
|
+
expect(subject.valid?).to eq(true)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "returns false if errors" do
|
182
|
+
expect(subject).to receive(:errors).and_return(["error"])
|
183
|
+
expect(subject.valid?).to eq(false)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "internals" do
|
188
|
+
describe "#all_attributes" do
|
189
|
+
it "doesn't respond to all_attributes" do
|
190
|
+
expect(subject.respond_to?(:all_attributes)).to eq(false)
|
191
|
+
end
|
192
|
+
|
193
|
+
it "returns all attributes and values" do
|
194
|
+
expect(subject.send(:all_attributes)).to eq(column_one: "Value One")
|
195
|
+
end
|
196
|
+
|
197
|
+
context "with multiple columns" do
|
198
|
+
let(:columns) { [
|
199
|
+
double("column", key: "column one", model_attribute: :column_one),
|
200
|
+
double("column", key: "column two", model_attribute: :column_two),
|
201
|
+
double("column", key: "column three", model_attribute: :column_three)
|
202
|
+
] }
|
203
|
+
let(:data) { ["Value One", "", "Value Three"] }
|
204
|
+
|
205
|
+
before do
|
206
|
+
allow(header).to receive(:column_index).with("column two").and_return(1)
|
207
|
+
allow(header).to receive(:column_index).with("column three").and_return(2)
|
208
|
+
end
|
209
|
+
|
210
|
+
it "returns all attributes and values" do
|
211
|
+
expect(subject.send(:all_attributes)).to eq({ column_one: "Value One", column_two: "", column_three: "Value Three" })
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe "#inherit_or_delegate" do
|
217
|
+
let(:model) { double("model") }
|
218
|
+
let(:subject) { described_class.new(header, data, model: model) }
|
219
|
+
|
220
|
+
it "doesn't respond to inherit_or_delegate" do
|
221
|
+
expect(subject.respond_to?(:inherit_or_delegate)).to eq(false)
|
222
|
+
end
|
223
|
+
|
224
|
+
it "returns nil when no method invoked" do
|
225
|
+
expect(subject.send(:inherit_or_delegate, :some_method, :multiple, :args)).to eq(nil)
|
226
|
+
end
|
227
|
+
|
228
|
+
it "invokes internal method if method is defined" do
|
229
|
+
allow(subject).to receive(:respond_to?).with(:some_method).and_return(true)
|
230
|
+
allow(subject).to receive(:respond_to?).with(:some_method, false).and_return(true)
|
231
|
+
expect(subject).to receive(:some_method).with(:multiple, :args).and_return(:some_value)
|
232
|
+
expect(subject.send(:inherit_or_delegate, :some_method, :multiple, :args)).to eq(:some_value)
|
233
|
+
end
|
234
|
+
|
235
|
+
it "invokes delegate method if delegate exists and internal method is not defined" do
|
236
|
+
expect(model).to receive(:some_method).with(:multiple, :args).and_return(:some_value)
|
237
|
+
expect(subject.send(:inherit_or_delegate, :some_method, :multiple, :args)).to eq(:some_value)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
describe "#key_attributes" do
|
242
|
+
it "doesn't respond to key_attributes" do
|
243
|
+
expect(subject.respond_to?(:key_attributes)).to eq(false)
|
244
|
+
end
|
245
|
+
|
246
|
+
it "returns the entire row if no primary key is defined" do
|
247
|
+
expect(subject.send(:key_attributes)).to eq(column_one: "Value One")
|
248
|
+
end
|
249
|
+
|
250
|
+
context "when the row has a single primary key column" do
|
251
|
+
let(:primary_key_columns) { [column] }
|
252
|
+
|
253
|
+
it "returns the value for the key column" do
|
254
|
+
expect(subject.send(:key_attributes)).to eq(column_one: "Value One")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context "when the row has a composite key" do
|
259
|
+
let(:columns) { [
|
260
|
+
double("column", key: "column one", model_attribute: :column_one),
|
261
|
+
double("column", key: "column two", model_attribute: :column_two),
|
262
|
+
double("column", key: "column three", model_attribute: :column_three)
|
263
|
+
] }
|
264
|
+
let(:primary_key_columns) { [
|
265
|
+
double("column", key: "column one", model_attribute: :column_one),
|
266
|
+
double("column", key: "column two", model_attribute: :column_two)
|
267
|
+
] }
|
268
|
+
let(:data) { ["Value One", "Value Two", "Value Three"] }
|
269
|
+
|
270
|
+
before do
|
271
|
+
allow(header).to receive(:column_index).with("column two").and_return(1)
|
272
|
+
end
|
273
|
+
|
274
|
+
it "returns the value for the key columns" do
|
275
|
+
expect(subject.send(:key_attributes)).to eq(column_one: "Value One", column_two: "Value Two")
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
describe "#model_instance" do
|
281
|
+
let(:model) { double("model") }
|
282
|
+
let(:model_instance) { double("model-instance") }
|
283
|
+
let(:subject) { described_class.new(header, data, model: model) }
|
284
|
+
|
285
|
+
before do
|
286
|
+
allow(subject).to receive(:key_attributes).and_return(:key_attributes)
|
287
|
+
end
|
288
|
+
|
289
|
+
it "doesn't respond to model_instance" do
|
290
|
+
expect(subject.respond_to?(:model_instance)).to eq(false)
|
291
|
+
end
|
292
|
+
|
293
|
+
it "first tries to find an instance via #find_row_model" do
|
294
|
+
allow(subject).to receive(:respond_to?).with(:find_row_model).and_return(true)
|
295
|
+
allow(subject).to receive(:respond_to?).with(:find_row_model, false).and_return(true)
|
296
|
+
expect(subject).to receive(:find_row_model).with(:key_attributes).and_return(model_instance)
|
297
|
+
expect(subject.send(:model_instance)).to eq(model_instance)
|
298
|
+
end
|
299
|
+
|
300
|
+
it "tries to find an instance via model#find_row_model when #find_row_model does not exist" do
|
301
|
+
expect(model).to receive(:find_row_model).with(:key_attributes).and_return(model_instance)
|
302
|
+
expect(subject.send(:model_instance)).to eq(model_instance)
|
303
|
+
end
|
304
|
+
|
305
|
+
it "tries to instantiate a new model via #new_row_model when a model cannot be found" do
|
306
|
+
allow(subject).to receive(:respond_to?).with(:find_row_model).and_return(true)
|
307
|
+
allow(subject).to receive(:respond_to?).with(:find_row_model, false).and_return(true)
|
308
|
+
allow(subject).to receive(:respond_to?).with(:new_row_model).and_return(true)
|
309
|
+
allow(subject).to receive(:respond_to?).with(:new_row_model, false).and_return(true)
|
310
|
+
expect(subject).to receive(:find_row_model).with(:key_attributes).and_return(nil)
|
311
|
+
expect(subject).to receive(:new_row_model).with(:key_attributes).and_return(model_instance)
|
312
|
+
expect(subject.send(:model_instance)).to eq(model_instance)
|
313
|
+
end
|
314
|
+
|
315
|
+
it "tries to instantiate a new model via model#new_row_model when a model cannot be found and #new_row_model does not exist" do
|
316
|
+
expect(model).to receive(:find_row_model).with(:key_attributes).and_return(nil)
|
317
|
+
expect(model).to receive(:new_row_model).with(:key_attributes).and_return(model_instance)
|
318
|
+
expect(subject.send(:model_instance)).to eq(model_instance)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
describe "#process_row" do
|
323
|
+
let(:model) { double("model") }
|
324
|
+
let(:model_instance) { double("model-instance", changed?: true, marked_for_destruction?: false, new_record?: false, valid?: true) }
|
325
|
+
let(:subject) { described_class.new(header, data, model: model) }
|
326
|
+
|
327
|
+
before do
|
328
|
+
allow(model).to receive(:find_row_model).and_return(model_instance)
|
329
|
+
allow(subject).to receive(:all_attributes).and_return(:all_attributes)
|
330
|
+
end
|
331
|
+
|
332
|
+
it "doesn't respond to process_row" do
|
333
|
+
expect(subject.respond_to?(:process_row)).to eq(false)
|
334
|
+
end
|
335
|
+
|
336
|
+
it "assigns attributes and saves the model instance" do
|
337
|
+
expect(model_instance).to receive(:assign_attributes).with(:all_attributes).ordered
|
338
|
+
expect(model_instance).to receive(:save)
|
339
|
+
subject.send(:process_row)
|
340
|
+
end
|
341
|
+
|
342
|
+
it "only processes a record once" do
|
343
|
+
expect(model_instance).to receive(:assign_attributes).with(:all_attributes).ordered
|
344
|
+
expect(model_instance).to receive(:save)
|
345
|
+
subject.send(:process_row)
|
346
|
+
subject.send(:process_row)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
end
|