g5_updatable 0.10.3 → 0.20.3.pre.1

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -2
  3. data/app/concerns/g5_updatable/belongs_to_client.rb +3 -3
  4. data/app/controllers/g5_updatable/feed_controller.rb +1 -1
  5. data/app/controllers/g5_updatable/locations_controller.rb +2 -2
  6. data/app/controllers/g5_updatable/syncs_controller.rb +3 -3
  7. data/app/models/g5_updatable/client.rb +4 -4
  8. data/app/models/g5_updatable/hub_amenities_location.rb +6 -0
  9. data/app/models/g5_updatable/hub_amenity.rb +21 -0
  10. data/app/models/g5_updatable/location.rb +75 -2
  11. data/app/serializers/g5_updatable/location_serializer.rb +8 -0
  12. data/db/migrate/20151103043916_add_latitude_and_longitude_to_location.rb +6 -0
  13. data/db/migrate/20151103050229_copy_lat_long_props_to_lat_long_columns.rb +19 -0
  14. data/db/migrate/20151106070749_add_latitude_longitude_indexes_to_location.rb +6 -0
  15. data/db/migrate/20161122070749_add_amenities.rb +25 -0
  16. data/db/migrate/20161209070749_add_client_urn_to_locations.rb +6 -0
  17. data/lib/g5_updatable.rb +5 -3
  18. data/lib/g5_updatable/all_client_urns_fetcher.rb +17 -0
  19. data/lib/g5_updatable/client_feed_processor.rb +26 -14
  20. data/lib/g5_updatable/client_updater.rb +37 -12
  21. data/lib/g5_updatable/factories.rb +2 -2
  22. data/lib/g5_updatable/fetcher.rb +22 -0
  23. data/lib/g5_updatable/indifferentizer.rb +11 -0
  24. data/lib/g5_updatable/locations_updater.rb +68 -20
  25. data/lib/g5_updatable/rspec/factories.rb +53 -2
  26. data/lib/g5_updatable/version.rb +1 -1
  27. data/lib/tasks/g5_updatable_tasks.rake +11 -4
  28. data/spec/concerns/g5_updatable/belongs_to_client_spec.rb +1 -3
  29. data/spec/controllers/feed_controller_spec.rb +21 -0
  30. data/spec/controllers/syncs_controller_spec.rb +3 -3
  31. data/spec/dummy/config/database.yml +2 -2
  32. data/spec/dummy/db/schema.rb +28 -1
  33. data/spec/dummy/log/development.log +172 -146
  34. data/spec/dummy/log/test.log +101011 -13426
  35. data/spec/dummy/log/tests.log +0 -0
  36. data/spec/fixtures/client-g5-c-1soj8m6e-g5-multifamily-missing-locations.json +121 -0
  37. data/spec/fixtures/client-g5-c-1soj8m6e-g5-multifamily-no-locations.json +41 -0
  38. data/spec/fixtures/client-g5-c-1soj8m6e-g5-multifamily.json +471 -0
  39. data/spec/fixtures/hub-client.json +187 -0
  40. data/spec/fixtures/hub-clients.json +19972 -0
  41. data/spec/fixtures/hub-location.json +166 -0
  42. data/spec/fixtures/location-g5-cl-1soj9pe2-541-apartments.json +98 -0
  43. data/spec/fixtures/urns.json +10 -0
  44. data/spec/lib/g5_updatable/all_client_urns_fetcher_spec.rb +56 -0
  45. data/spec/lib/g5_updatable/client_feed_processor_spec.rb +86 -33
  46. data/spec/lib/g5_updatable/client_updater_spec.rb +55 -23
  47. data/spec/lib/g5_updatable/locations_updater_spec.rb +140 -54
  48. data/spec/models/g5_updatable/client_spec.rb +2 -0
  49. data/spec/models/g5_updatable/hub_amenities_location_spec.rb +6 -0
  50. data/spec/models/g5_updatable/hub_amenity_spec.rb +29 -0
  51. data/spec/models/g5_updatable/location_spec.rb +240 -10
  52. data/spec/serializers/g5_updatable/location_serializer_spec.rb +2 -1
  53. data/spec/spec_helper.rb +2 -1
  54. data/spec/support/fixture_helper.rb +5 -0
  55. data/spec/support/shared_examples/belongs_to_client.rb +4 -5
  56. metadata +84 -17
@@ -1,23 +1,32 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe G5Updatable::ClientUpdater do
4
- let(:uid) { "http://example.com/uid" }
4
+ let(:uid) { 'http://example.com/uid' }
5
+ let(:urn) { 'urn' }
5
6
  let(:properties) { {uid: uid,
6
- urn: "urn",
7
- name: "Client Name", } }
8
- let(:g5_client) { G5FoundationClient::Client.new(properties) }
7
+ urn: urn,
8
+ name: 'Client Name', } }
9
9
 
10
- let(:updater) { described_class.new(g5_client) }
10
+ let(:updater) { described_class.new(properties) }
11
11
 
12
- describe "#update" do
12
+ describe '#update' do
13
+ let(:callback_1) { Proc.new { |g5_client| @callback_1_called = true } }
14
+ let(:callback_2) { Proc.new { |g5_client| @callback_2_called = true } }
13
15
  subject { G5Updatable::Client.first }
14
16
 
15
- context "with no existing Client records" do
17
+ before do
18
+ described_class.on_create(&callback_1)
19
+ described_class.on_create(&callback_2)
20
+ @callback_1_called = false
21
+ @callback_2_called = false
22
+ end
23
+
24
+ context 'with no existing Client records' do
16
25
  before { updater.update }
17
26
 
18
27
  let(:name) { 'Client Name' }
19
28
 
20
- it "creates a Client" do
29
+ it 'creates a Client' do
21
30
  expect(G5Updatable::Client.count).to eq(1)
22
31
  end
23
32
 
@@ -25,40 +34,63 @@ describe G5Updatable::ClientUpdater do
25
34
  expect(subject.properties.keys.collect(&:to_sym)).to eq(properties.keys)
26
35
  end
27
36
 
28
- its(:uid) { should eq(uid) }
29
- its(:urn) { should eq("urn") }
30
- its(:name) { should eq(name) }
37
+ its(:uid) { is_expected.to eq(uid) }
38
+ its(:urn) { is_expected.to eq('urn') }
39
+ its(:name) { is_expected.to eq(name) }
31
40
 
32
41
  it 'sets name field so that consumers can easily sort by name' do
33
42
  subject
34
43
  expect(G5Updatable::Client.find_by_name(name)).to eq(subject)
35
44
  end
45
+
46
+ it 'fires the specified on_create callbacks' do
47
+ expect(@callback_1_called).to be true
48
+ expect(@callback_2_called).to be true
49
+ end
36
50
  end
37
51
 
38
- context "with an existing Client record" do
52
+ context 'with an existing Client record' do
39
53
  before do
40
- FactoryGirl.create(:client, uid: uid, urn: "old")
54
+ FactoryGirl.create(:client, uid: 'old', urn: urn)
41
55
  updater.update
42
56
  end
43
57
 
44
- it "does not create a new Client" do
58
+ it 'does not create a new Client' do
45
59
  expect(G5Updatable::Client.count).to eq(1)
46
60
  end
47
61
 
48
- its(:urn) { should eq("urn") }
49
- its(:name) { should eq("Client Name") }
50
- end
62
+ its(:uid) { is_expected.to eq(uid) }
63
+ its(:name) { is_expected.to eq('Client Name') }
51
64
 
52
- context "with an existing identical Client record" do
65
+ it 'does not fire any on_create callbacks' do
66
+ expect(@callback_1_called).to be false
67
+ expect(@callback_2_called).to be false
68
+ end
69
+ end
70
+
71
+ context 'with an existing identical Client record' do
53
72
  before do
54
- client = FactoryGirl.create(:client, uid: uid, urn: "urn", name: "Client Name", properties: properties)
73
+ FactoryGirl.create(:client, uid: uid, urn: 'urn', name: 'Client Name', properties: properties)
55
74
  @original_updated_at = subject.updated_at
56
75
  updater.update
57
- end
76
+ end
58
77
 
59
- it "updates its timestamp" do
78
+ it 'updates its timestamp' do
60
79
  expect(subject.reload.updated_at).to be > @original_updated_at
61
80
  end
62
- end
81
+ end
82
+
83
+ describe 'with locations' do
84
+ let(:created_client) { G5Updatable::Client.last }
85
+
86
+ let(:properties) do
87
+ JSON.parse(fixture('client-g5-c-1soj8m6e-g5-multifamily.json'))['client']
88
+ end
89
+
90
+ it 'creates the locations' do
91
+ expect { updater.update }.to change { G5Updatable::Location.count }.by(5)
92
+ expect(created_client.locations.collect(&:urn)).to match_array(%w(g5-cl-1soj9pe2-541-apartments g5-cl-1soja3fn-99-apartments g5-cl-560belmk8-denali g5-cl-i2xqm52t-mockup-design-3 g5-cl-i2gg3rv8-g5-apartments))
93
+ end
94
+ end
63
95
  end
64
96
  end
@@ -1,24 +1,22 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe G5Updatable::LocationsUpdater do
4
- let(:uid) { "http://example.com/uid" }
5
- let(:properties) { {uid: uid,
6
- urn: "urn",
7
- client_uid: "client_uid",
8
- name: "Location Name"} }
9
- let(:g5_location) { G5FoundationClient::Location.new(properties) }
10
- let(:g5_locations) { [g5_location] }
11
- let(:g5_foundation_client) { G5FoundationClient::Client.new(urn: "urn", uid: "client_uid") }
12
-
13
- let(:updater) { described_class.new(g5_foundation_client) }
14
-
15
- before do
16
- allow(g5_foundation_client).to receive(:locations) { g5_locations }
4
+ let(:name) { properties['name'] }
5
+ let(:uid) { properties['uid'] }
6
+ let(:client_uid) { properties['client_uid'] }
7
+ let(:client_urn) { properties['client_uid'].split('/').last }
8
+ let(:urn) { properties['urn'] }
9
+ let(:latitude) { properties['latitude'] }
10
+ let(:longitude) { properties['longitude'] }
11
+ let(:properties) do
12
+ JSON.parse(fixture('location-g5-cl-1soj9pe2-541-apartments.json'))['location']
17
13
  end
14
+ let(:options) { {} }
15
+ let(:updater) { described_class.new(properties, options) }
18
16
 
19
17
  shared_examples 'does not destroy existing clients and locations' do
20
18
  let!(:existing_client) { FactoryGirl.create(:client) }
21
- let!(:existing_location) { FactoryGirl.create(:location, client_uid: existing_client.uid)}
19
+ let!(:existing_location) { FactoryGirl.create(:location, client_uid: existing_client.uid) }
22
20
 
23
21
  it 'does not destroy the existing client or location' do
24
22
  expect(G5Updatable::Client.find(existing_client.id)).to_not be_nil
@@ -27,62 +25,94 @@ describe G5Updatable::LocationsUpdater do
27
25
  end
28
26
 
29
27
  describe "#update" do
30
-
31
-
32
28
  subject { G5Updatable::Location.last }
33
29
 
34
30
  context "with no existing Location records" do
35
31
  before { updater.update }
36
32
 
37
- let(:name) { 'Location Name' }
38
-
39
33
  it "creates a Location" do
40
34
  expect(G5Updatable::Location.count).to eq(1)
41
35
  end
42
36
 
43
37
 
44
- it 'does not redact keys in properties' do
45
- expect(subject.properties.keys.collect(&:to_sym)).to eq(properties.keys)
38
+ it 'redacts amenities key' do
39
+ expect(subject.properties.keys.collect(&:to_sym)).to eq(properties.keys.collect(&:to_sym) - [:amenities])
46
40
  end
47
41
 
48
- its(:uid) { should eq(uid) }
49
- its(:urn) { should eq("urn") }
50
- its(:client_uid) { should eq("client_uid") }
51
- its(:name) { should eq(name) }
42
+ its(:uid) { is_expected.to eq(uid) }
43
+ its(:urn) { is_expected.to eq(urn) }
44
+ its(:client_uid) { is_expected.to eq(client_uid) }
45
+ its(:client_urn) { is_expected.to eq(client_urn
46
+ ) }
47
+ its(:name) { is_expected.to eq(name) }
48
+ its(:latitude) { is_expected.to eq(latitude) }
49
+ its(:longitude) { is_expected.to eq(longitude) }
52
50
 
53
51
  it 'sets name field so that consumers can easily sort by name' do
54
52
  subject
55
53
  expect(G5Updatable::Location.find_by_name(name)).to eq(subject)
56
54
  end
57
55
 
56
+ it 'creates amenities' do
57
+ subject
58
+ expect(subject.hub_amenities.count).to eq(2)
59
+ expect(subject.hub_amenities.collect(&:name)).to match_array(['Power', 'WIFI'])
60
+ expect(subject.hub_amenities.collect(&:icon)).to match_array(%w(fa-electric-outlet fa-wifi))
61
+ expect(subject.hub_amenities.first.external_updated_at).to be_present
62
+ expect(subject.hub_amenities.first.external_created_at).to be_present
63
+ end
64
+
58
65
  it_behaves_like 'does not destroy existing clients and locations'
59
66
  end
60
67
 
61
68
 
62
- context "with an existing Location record" do
69
+ context 'with an existing Location record' do
70
+ let(:old_amenity) { create(:hub_amenity) }
63
71
  before do
64
- FactoryGirl.create(:location, uid: uid, urn: "old")
72
+ location = FactoryGirl.create(:location, uid: 'old', urn: urn, client_uid: client_urn)
73
+ location.hub_amenities << old_amenity
65
74
  updater.update
66
75
  end
67
76
 
68
- it "does not create a new Location" do
77
+ it 'does not create a new Location' do
69
78
  expect(G5Updatable::Location.count).to eq(1)
70
79
  end
71
80
 
81
+ its(:urn) { is_expected.to eq(urn) }
82
+ its(:name) { is_expected.to eq name }
72
83
 
84
+ it 'updates amenities' do
85
+ expect(subject.hub_amenities.collect(&:name)).to match_array(['Power', 'WIFI'])
86
+ end
73
87
 
74
- its(:urn) { should eq("urn") }
75
- its(:name) { should eq("Location Name") }
88
+ it 'sets flat amenities for easier exclusive querying' do
89
+ expect(subject.flat_amenity_names).to eq('|Power|WIFI|')
90
+ end
76
91
 
77
92
  it_behaves_like 'does not destroy existing clients and locations'
93
+
94
+ context 'destroy_orphaned_locations set to false' do
95
+ before do
96
+ expect(updater).to_not receive :destroy_orphaned_locations!
97
+ end
98
+
99
+ it "does not create a new Location" do
100
+ expect(G5Updatable::Location.count).to eq(1)
101
+ end
102
+
103
+ its(:urn) { is_expected.to eq(urn) }
104
+ its(:name) { is_expected.to eq name }
105
+
106
+ it_behaves_like 'does not destroy existing clients and locations'
107
+ end
78
108
  end
79
109
 
80
110
  context "with an existing identical Location record" do
81
111
  before do
82
- loc = FactoryGirl.create(:location, uid: uid, urn: "urn", client_uid: "client_uid", name: "Location Name", properties: properties)
112
+ FactoryGirl.create(:location, uid: uid, urn: urn, client_uid: "client_uid", name: name, properties: properties, updated_at: 1.hour.ago)
83
113
  @original_updated_at = subject.updated_at
84
114
  updater.update
85
- end
115
+ end
86
116
 
87
117
  it "updates its timestamp" do
88
118
  expect(subject.reload.updated_at).to be > @original_updated_at
@@ -90,47 +120,69 @@ describe G5Updatable::LocationsUpdater do
90
120
 
91
121
  it_behaves_like 'does not destroy existing clients and locations'
92
122
 
93
- end
123
+ end
94
124
 
95
125
  context "with no locations" do
96
126
  let(:g5_locations) { [] }
127
+ let(:options) { {destroy_orphaned_locations: true} }
97
128
 
98
129
  before do
99
- FactoryGirl.create(:location, uid: uid, urn: "another_urn", client_uid: "client_uid", name: "Location Name", properties: properties)
100
- FactoryGirl.create(:location, uid: uid, urn: "some_urn", client_uid: "another_client_uid", name: "Location Name", properties: properties)
130
+ FactoryGirl.create(:location, uid: uid, urn: "another_urn", client_urn: client_urn, client_uid: "client_uid", name: name, properties: properties)
131
+ FactoryGirl.create(:location, uid: uid, urn: "some_urn", client_uid: "another_client_uid", name: name, properties: properties)
101
132
  updater.update
102
- end
133
+ end
103
134
 
104
135
  it "deletes locations" do
105
136
  expect(G5Updatable::Location.by_client_uid("client_uid").count).to eq(0)
106
137
  expect(G5Updatable::Location.by_client_uid("another_client_uid").count).to eq(1)
107
- end
108
- end
138
+ end
139
+ end
109
140
 
110
141
  context "with an orphaned location" do
142
+ let(:options) { {destroy_orphaned_locations: true} }
111
143
  before do
112
- FactoryGirl.create(:location, uid: "another_uid", urn: "dead_urn", client_uid: "client_uid")
144
+ FactoryGirl.create(:location, uid: "another_uid", urn: "dead_urn", client_urn: client_urn)
113
145
  updater.update
114
- end
146
+ end
115
147
 
116
148
  it "deletes locations" do
117
- expect(G5Updatable::Location.by_urn("urn").by_client_uid("client_uid").count).to eq(1)
118
- expect(G5Updatable::Location.by_urn("dead_urn").by_client_uid("client_uid").count).to eq(0)
119
- end
120
- end
149
+ expect(G5Updatable::Location.by_urn(urn).by_client_urn(client_urn).count).to eq(1)
150
+ expect(G5Updatable::Location.by_urn("dead_urn").by_client_urn(client_urn).count).to eq(0)
151
+ end
152
+ end
121
153
 
122
154
  context "callbacks" do
123
155
  before do
124
- FactoryGirl.create(:location, uid: uid, urn: "old")
125
- @callback_called = false
156
+ FactoryGirl.create(:location, uid: 'old', urn: urn)
157
+ @update_callback_called = false
158
+ @create_callback_called = false
159
+ described_class.on_update { |_location| @update_callback_called = true }
160
+ described_class.on_create { |_location| @create_callback_called = true }
126
161
  end
127
162
 
128
- it "calls callbacks passing the location that was updated" do
129
- described_class.on_update { |location| @callback_called = true }
163
+ describe "update callbacks" do
164
+ it "calls update callbacks passing the location that was updated" do
165
+ expect(@update_callback_called).to be false
166
+ updater.update
167
+ expect(@update_callback_called).to be true
168
+ end
169
+ end
130
170
 
131
- expect(@callback_called).to be false
132
- updater.update
133
- expect(@callback_called).to be true
171
+ describe "create callbacks" do
172
+ it "skips create callbacks when the location already exists" do
173
+ updater.update
174
+ expect(@create_callback_called).to be false
175
+ end
176
+
177
+ context 'with some new locations' do
178
+ let(:new_properties) do
179
+ JSON.parse(fixture('client-g5-c-1soj8m6e-g5-multifamily.json'))['client']['locations']
180
+ end
181
+ it "calls create callbacks for a new location" do
182
+ described_class.new(new_properties, options).update
183
+ expect(@create_callback_called).to be true
184
+ end
185
+ end
134
186
  end
135
187
  end
136
188
  end
@@ -141,18 +193,18 @@ describe G5Updatable::LocationsUpdater do
141
193
  end
142
194
 
143
195
  it "adds to the #update callbacks" do
144
- callback_1 = -> (location) {}
145
- callback_2 = -> (location) {}
196
+ callback_1 = -> (location, anything) {}
197
+ callback_2 = -> (location, anything) {}
146
198
  described_class.on_update(&callback_1)
147
199
  described_class.on_update(&callback_2)
148
200
 
149
201
  expect(described_class.on_update_callbacks).
150
- to eq([callback_1, callback_2])
202
+ to eq([callback_1, callback_2])
151
203
  end
152
204
 
153
205
  it "passes the location into each callback" do
154
206
  accumulator = []
155
- callback = -> (location) { accumulator << location.id }
207
+ callback = -> (location, anything) { accumulator << location.id }
156
208
  described_class.on_update(&callback)
157
209
 
158
210
  updater.update
@@ -169,4 +221,38 @@ describe G5Updatable::LocationsUpdater do
169
221
  end
170
222
  end
171
223
 
224
+ describe ".on_create and .on_create_callbacks" do
225
+ before do
226
+ described_class.on_create_callbacks = []
227
+ end
228
+
229
+ it "adds to the #update callbacks" do
230
+ callback_1 = -> (location) {}
231
+ callback_2 = -> (location) {}
232
+ described_class.on_create(&callback_1)
233
+ described_class.on_create(&callback_2)
234
+
235
+ expect(described_class.on_create_callbacks).
236
+ to eq([callback_1, callback_2])
237
+ end
238
+
239
+ it "passes the location into each callback" do
240
+ accumulator = []
241
+ callback = -> (location) { accumulator << location.id }
242
+ described_class.on_create(&callback)
243
+
244
+ updater.update
245
+ location = G5Updatable::Location.first
246
+
247
+ expect(accumulator).to include(location.id)
248
+ end
249
+ end
250
+
251
+ describe ".on_create_callbacks" do
252
+ it "defaults to an empty array" do
253
+ described_class.on_create_callbacks = nil
254
+ expect(described_class.on_create_callbacks).to be_empty
255
+ end
256
+ end
257
+
172
258
  end
@@ -6,6 +6,8 @@ describe G5Updatable::Client do
6
6
 
7
7
  it { expect(client).to validate_presence_of(:uid) }
8
8
  it { expect(client).to validate_presence_of(:urn) }
9
+ it { expect(client).to validate_uniqueness_of(:urn) }
10
+ it { expect(client).to have_many :locations }
9
11
  end
10
12
 
11
13
  it_behaves_like "a model with first-class properties" do
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe G5Updatable::HubAmenitiesLocation do
4
+ it { is_expected.to belong_to :location }
5
+ it { is_expected.to belong_to :hub_amenity }
6
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe G5Updatable::HubAmenity do
4
+ it { is_expected.to validate_presence_of :external_id }
5
+
6
+ describe 'scopes' do
7
+ let!(:location) { create(:g5mf_location) }
8
+ let!(:amenity) { create(:hub_amenity) }
9
+ let!(:amenity2) { create(:hub_amenity) }
10
+ let!(:amenity3) { create(:hub_amenity) }
11
+
12
+ before do
13
+ location.hub_amenities << amenity
14
+ location.hub_amenities << amenity2
15
+ end
16
+
17
+ describe '#by_client_urn' do
18
+ specify { expect(described_class.by_client_urn(location.client_urn)).to match_array([amenity, amenity2]) }
19
+ end
20
+
21
+ describe '#by_client_urns' do
22
+ specify { expect(described_class.by_client_urns([location.client_urn])).to match_array([amenity, amenity2]) }
23
+ end
24
+
25
+ describe '#by_client_uid' do
26
+ specify { expect(described_class.by_client_uid(location.client_uid)).to match_array([amenity, amenity2]) }
27
+ end
28
+ end
29
+ end