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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3afda87d522137bbbd0e889b22b17ba948be94ff
4
- data.tar.gz: f7baac28c58eb4cdcf78ab270a23aeedeb24c46a
3
+ metadata.gz: fff24c03cbb209168e956147a0266737b9bcfba9
4
+ data.tar.gz: b459baf96623cb716ed624e606307f1712359d40
5
5
  SHA512:
6
- metadata.gz: 58b4ce1062ed6e2a9ca0282e9f56307bedcee380600d58959dac5de02bbda2b5f62bd8300c4f63088931d1e2974ca1abd678302f85bca9755ed414be4af4ced6
7
- data.tar.gz: 129bb7ea507eb372da5be65b9bae5397cc4a92629cc074e3b4ff76251ab2f13420b324d593db5f53ae95055ec4e44edc234abf7b38e36292abbcd7ecf09c36da
6
+ metadata.gz: fccf2277a17f67d1c7945ef14c90084b60eb11d42e5937352b4cefe52b45fa79d704f857329593df4bddf78a3571d0987b4b731bf0424d1e30c1e7b1ce35e825
7
+ data.tar.gz: c550dc2f70ec537c76570849bc2f54b637367b5ad5c9c55bf59dae1bae1d1d2acf99adb11b97ef4d97b70993a50ebd74829106e3e7028056be686db8af4aac0c
data/README.md CHANGED
@@ -38,9 +38,12 @@ G5 Updatable makes use of PostgrSQL's `json` field type, and so only supports im
38
38
  ```
39
39
  3. Optional: load all of G5-Hub's data into your database
40
40
 
41
+ **Important:** The entries.js and entries.json endpoint on the hub are deprecated. So, for all versions of g5_updatable before 0.17.0, this task will only load the newest 10 clients. If you would like to load all clients, please specify "~> 0.17.0" or higher in your Gemfile.
42
+
41
43
  ```console
42
44
  rake g5_updatable:load_all
43
- ```
45
+ ```
46
+
44
47
  Note, all of the G5_AUTH env variables need to be set for this to work.
45
48
 
46
49
  ## Usage
@@ -58,14 +61,42 @@ G5 Updatable also exposes two GET endpoints which return JSON:
58
61
 
59
62
  ### Hooks
60
63
 
64
+ Execute code each time a new client is created:
65
+
66
+ ```ruby
67
+ G5Updatable::ClientUpdater.on_create do |g5_updatable_client|
68
+ puts "#{g5_updatable_client.name} was just created"
69
+ end
70
+ ```
71
+
61
72
  To execute code every time a location is updated:
62
73
 
63
74
  ```ruby
64
- G5Updatable::LocationsUpdader.on_update do |g5_updatable_location|
75
+ G5Updatable::LocationsUpdater.on_update do |g5_updatable_location|
76
+ puts "G5Updatable::Location##{g5_updatable_location.id} was just updated"
77
+ end
78
+ ```
79
+
80
+ Also optionally expect a secondary block argument, receiving the object previous to be updated
81
+ ... you know.. For diff reasons.
82
+ ```ruby
83
+ G5Updatable::LocationsUpdater.on_update do |g5_updatable_location, before_update_location|
84
+ puts "G5Updatable::Location##{before_update_location.properties} changed to "
85
+ puts "G5Updatable::Location##{g5_updatable_location.properties} "
86
+ end
87
+ ```
88
+
89
+
90
+ or, execute code only when a location is created:
91
+
92
+ ```ruby
93
+ G5Updatable::LocationsUpdater.on_create do |g5_updatable_location|
65
94
  puts "G5Updatable::Location##{g5_updatable_location.id} was just updated"
66
95
  end
67
96
  ```
68
97
 
98
+ The `on_create` callback is called right before the `on_update` callback when a new location is created.
99
+
69
100
  ### Associations
70
101
 
71
102
  You will likely have models in your own application that are associated with a Client or Location. A module is available to include in your related models to support this association. Assuming your model has a `client_uid` or `location_uid` string field respectively, you can do the following:
@@ -4,9 +4,9 @@ module G5Updatable::BelongsToClient
4
4
 
5
5
  included do
6
6
  belongs_to(:client, {
7
- class_name: "G5Updatable::Client",
8
- foreign_key: :client_uid,
9
- primary_key: :uid,
7
+ class_name: 'G5Updatable::Client',
8
+ foreign_key: :client_urn,
9
+ primary_key: :urn,
10
10
  })
11
11
  end
12
12
 
@@ -1,6 +1,6 @@
1
1
  class G5Updatable::FeedController < G5Updatable::BaseUpdatableController
2
2
  def update
3
- G5Updatable::ClientFeedProcessor.new(params[:client_uid]).work
3
+ G5Updatable::ClientFeedProcessor.new(params).work
4
4
  render json: {}, status: :ok
5
5
  end
6
6
  end
@@ -1,6 +1,6 @@
1
1
  class G5Updatable::LocationsController < G5Updatable::BaseUpdatableController
2
2
  def show
3
3
  @location = G5Updatable::Location.find_by_urn(params[:urn])
4
- render json: @location
4
+ render json: { location: @location }
5
5
  end
6
- end
6
+ end
@@ -4,16 +4,16 @@ class G5Updatable::SyncsController < G5Updatable::BaseUpdatableController
4
4
 
5
5
  def index
6
6
  timestamp = @client.locations.max_updated_at
7
- render json: {updated_at: formatted_timestamp(timestamp)}
7
+ render json: { updated_at: formatted_timestamp(timestamp) }
8
8
  end
9
9
 
10
10
  private
11
11
 
12
12
  def set_client
13
13
  @client = G5Updatable::Client.find_by_urn!(params[:urn])
14
- end
14
+ end
15
15
 
16
16
  def formatted_timestamp(timestamp)
17
17
  timestamp.in_time_zone.strftime("%I:%M%P on %B %e, %Y") if timestamp
18
- end
18
+ end
19
19
  end
@@ -4,11 +4,11 @@ module G5Updatable
4
4
  include G5Updatable::UrnAsParameter
5
5
 
6
6
  has_many(:locations, {
7
- class_name: "G5Updatable::Location",
8
- foreign_key: :client_uid,
9
- primary_key: :uid,
7
+ class_name: 'G5Updatable::Location',
8
+ foreign_key: :client_urn,
9
+ primary_key: :urn,
10
10
  })
11
11
 
12
- validates :uid, :urn, presence: true
12
+ validates :uid, :urn, presence: true, uniqueness: true
13
13
  end
14
14
  end
@@ -0,0 +1,6 @@
1
+ module G5Updatable
2
+ class HubAmenitiesLocation < ActiveRecord::Base
3
+ belongs_to :hub_amenity, foreign_key: :g5_updatable_hub_amenity_id
4
+ belongs_to :location, foreign_key: :g5_updatable_location_id
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module G5Updatable
2
+ class HubAmenity < ActiveRecord::Base
3
+ validates :external_id, presence: true
4
+ has_many :hub_amenities_locations, foreign_key: :g5_updatable_hub_amenity_id
5
+
6
+ scope :by_client_urn, -> (client_urn) {
7
+ joins(hub_amenities_locations: { location: :client }).
8
+ where('g5_updatable_clients.urn = :client_urn', client_urn: client_urn).uniq
9
+ }
10
+
11
+ scope :by_client_urns, -> (client_urns) {
12
+ joins(hub_amenities_locations: { location: :client }).
13
+ where('g5_updatable_clients.urn IN (:client_urns)', client_urns: client_urns.join(',')).uniq
14
+ }
15
+
16
+ scope :by_client_uid, -> (client_uid) {
17
+ joins(hub_amenities_locations: :location).
18
+ where('g5_updatable_locations.client_uid = :client_uid', client_uid: client_uid).uniq
19
+ }
20
+ end
21
+ end
@@ -4,18 +4,91 @@ module G5Updatable
4
4
  include G5Updatable::UrnAsParameter
5
5
  include BelongsToClient
6
6
 
7
- validates :uid, :urn, :client_uid, presence: true
7
+ has_many :hub_amenities_locations, foreign_key: :g5_updatable_location_id, dependent: :destroy
8
+ has_many :hub_amenities, through: :hub_amenities_locations
9
+
10
+ validates :uid, :urn, :client_uid, :client_urn, presence: true
11
+ validates :urn, uniqueness: true
8
12
 
9
13
  scope :by_client_uid, -> (client_uid) { where(client_uid: client_uid) }
14
+ scope :by_client_urn, -> (client_urn) { where(client_urn: client_urn) }
15
+ scope :by_postal_code, -> (postal_code) { by_property('postal_code', postal_code) }
16
+ scope :by_city, -> (city) { by_property('city', city) }
17
+ scope :by_state, -> (state) { by_property('state', state) }
18
+ scope :in_status, -> (status) { where("g5_updatable_locations.properties ->> 'status' IN (?)", status) }
19
+ scope :by_neighborhood, -> (neighborhood) {
20
+ where("g5_updatable_locations.properties ->> 'neighborhood' = :neighborhood OR g5_updatable_locations.properties ->> 'neighborhood_2' = :neighborhood", neighborhood: neighborhood)
21
+ }
22
+ scope :by_property, -> (property_name, value) { where("g5_updatable_locations.properties ->> '#{property_name}' = :#{property_name}", property_name.to_sym => value) }
10
23
  scope :by_urn, -> (urn) { where(urn: urn) }
11
24
  scope :max_updated_at, -> { maximum(:updated_at) }
25
+ scope :by_amenity_names, -> (*amenity_names) { # there must be a match for every amenity name (AND not OR)
26
+ results = all
27
+ [amenity_names].flatten.each do |amenity_name|
28
+ results = results.where('g5_updatable_locations.flat_amenity_names LIKE :amenity_names',
29
+ amenity_names: "%#{amenity_names_to_tokenized_string(amenity_name)}%")
30
+ end
31
+ results
32
+ }
33
+ before_validation :set_client_urn, :set_client_uid
34
+
35
+ def refresh_flat_amenity_names!
36
+ update_attributes(flat_amenity_names: self.class.amenity_names_to_tokenized_string(hub_amenities.collect(&:name)))
37
+ end
12
38
 
13
- delegate :urn, to: :client, prefix: true, allow_nil: true
39
+ def amenity_names_array=(names_array)
40
+ self.flat_amenity_names = self.class.amenity_names_to_tokenized_string names_array
41
+ end
42
+
43
+ FLAT_DELIM = '|'
44
+
45
+ def self.amenity_names_to_tokenized_string(names_array)
46
+ return if names_array.blank?
47
+ "#{FLAT_DELIM}#{[names_array].flatten.compact.sort.join(FLAT_DELIM)}#{FLAT_DELIM}"
48
+ end
14
49
 
15
50
  def display_name
16
51
  return nil unless properties
17
52
  return properties['internal_branded_name'] if properties['internal_branded_name'].present?
18
53
  name
19
54
  end
55
+
56
+ def neighborhoods
57
+ [properties['neighborhood'], properties['neighborhood_2']].select(&:present?)
58
+ end
59
+
60
+ def live?
61
+ status?("Live")
62
+ end
63
+
64
+ def no_deploy?
65
+ status?("No Deploy")
66
+ end
67
+
68
+ def pending?
69
+ status?("Pending")
70
+ end
71
+
72
+ def deleted?
73
+ status?("Deleted")
74
+ end
75
+
76
+ def suspended?
77
+ status?("Suspended")
78
+ end
79
+
80
+ def status?(status)
81
+ return false unless properties
82
+ match = properties['status'].to_s.strip =~ /#{status.to_s.strip}/i
83
+ !!match
84
+ end
85
+
86
+ def set_client_urn
87
+ self.client_urn ||= self.client_uid.split('/').last if self.client_uid
88
+ end
89
+
90
+ def set_client_uid
91
+ self.client_uid||= self.client.try(:uid)
92
+ end
20
93
  end
21
94
  end
@@ -9,4 +9,12 @@ class G5Updatable::LocationSerializer < ActiveModel::Serializer
9
9
 
10
10
  keys
11
11
  end
12
+
13
+ def attributes(requested_attrs = nil, reload = false)
14
+ data = super requested_attrs, reload
15
+ object.properties.each do |name, value|
16
+ data[name.to_sym] = value
17
+ end
18
+ data
19
+ end
12
20
  end
@@ -0,0 +1,6 @@
1
+ class AddLatitudeAndLongitudeToLocation < ActiveRecord::Migration
2
+ def change
3
+ add_column :g5_updatable_locations, :latitude, :float
4
+ add_column :g5_updatable_locations, :longitude, :float
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ class CopyLatLongPropsToLatLongColumns < ActiveRecord::Migration
2
+ def up
3
+ G5Updatable::Location.all.each do |location|
4
+ location.latitude = location.properties['latitude']
5
+ location.longitude = location.properties['longitude']
6
+ location.save
7
+ end
8
+ end
9
+
10
+ def down
11
+ G5Updatable::Location.all.each do |location|
12
+ location.properties['latitude'] = location.latitude
13
+ location.proper['longitude'] = location.longitude
14
+ location.latitude = nil
15
+ location.longitude = nil
16
+ location.save
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ class AddLatitudeLongitudeIndexesToLocation < ActiveRecord::Migration
2
+ def change
3
+ add_index :g5_updatable_locations, :latitude
4
+ add_index :g5_updatable_locations, :longitude
5
+ end
6
+ end
@@ -0,0 +1,25 @@
1
+ class AddAmenities < ActiveRecord::Migration
2
+ def change
3
+ create_table :g5_updatable_hub_amenities do |t|
4
+ t.integer :external_id
5
+ t.string :name
6
+ t.string :icon
7
+ t.timestamp :external_updated_at
8
+ t.timestamp :external_created_at
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :g5_updatable_hub_amenities, :external_id, unique: true
13
+
14
+ create_table :g5_updatable_hub_amenities_locations do |t|
15
+ t.belongs_to :g5_updatable_hub_amenity
16
+ t.belongs_to :g5_updatable_location
17
+ end
18
+
19
+ add_index :g5_updatable_hub_amenities_locations, :g5_updatable_hub_amenity_id, name: 'updatable_amenities_loc_amen_id'
20
+ add_index :g5_updatable_hub_amenities_locations, :g5_updatable_location_id, name: 'updatable_amenities_loc_loc_id'
21
+
22
+ # we need this for queries that require a location to have ALL amenities in a list
23
+ add_column :g5_updatable_locations, :flat_amenity_names, :string
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ class AddClientUrnToLocations < ActiveRecord::Migration
2
+ def change
3
+ add_column :g5_updatable_locations, :client_urn, :string
4
+ add_index :g5_updatable_locations, :client_urn
5
+ end
6
+ end
data/lib/g5_updatable.rb CHANGED
@@ -1,6 +1,8 @@
1
- require "active_model_serializers"
2
- require "g5_foundation_client"
3
- require "g5_updatable/engine"
1
+ require 'active_model_serializers'
2
+ require 'g5_authentication_client'
3
+ require 'g5_updatable/engine'
4
+ require 'httparty'
4
5
 
5
6
  module G5Updatable
7
+ HUB_URL = ENV.fetch('HUB_URL', 'https://g5-hub.herokuapp.com').freeze
6
8
  end
@@ -0,0 +1,17 @@
1
+ module G5Updatable
2
+ class AllClientUrnsFetcher
3
+ def self.fetch_uids
4
+ G5Updatable::Fetcher.get_with_token(url)['clients'].collect do |urn|
5
+ "#{hub_url}/clients/#{urn['urn']}.json"
6
+ end
7
+ end
8
+
9
+ def self.url
10
+ "#{hub_url}/urns.json"
11
+ end
12
+
13
+ def self.hub_url
14
+ G5Updatable::HUB_URL
15
+ end
16
+ end
17
+ end
@@ -1,37 +1,49 @@
1
1
  class G5Updatable::ClientFeedProcessor
2
2
  attr_reader :client_uid
3
3
 
4
- def initialize(client_uid=nil)
5
- @client_uid = client_uid || ENV["CLIENT_UID"]
4
+ def initialize(params)
5
+ @client_uid = params[:client_uid] || ENV["CLIENT_UID"]
6
6
  raise "A client_uid must be either passed in or configured!" if @client_uid.blank?
7
+ @location_uid = params[:location_uid]
7
8
  end
8
9
 
9
10
  class << self
10
- def load_all_clients(clients_url)
11
- client_uids = G5FoundationClient::Client.all_client_uids clients_url
12
- client_uids.collect { |client_uid| new(client_uid).work }
11
+ def load_all_clients
12
+ G5Updatable::AllClientUrnsFetcher.fetch_uids.each do |uid|
13
+ puts "Loading: #{uid}"
14
+ load_client(uid)
15
+ end
16
+ end
17
+
18
+ def load_client(client_uid)
19
+ begin
20
+ new(client_uid: client_uid).work
21
+ rescue => e
22
+ Rails.logger.error e
23
+ nil
24
+ end
13
25
  end
14
26
  end
15
27
 
16
28
  def work
17
- client = update_client
18
- update_locations
19
-
20
- client
29
+ @location_uid ? update_location : update_client
21
30
  end
22
31
 
23
32
  private
24
33
 
25
- def update_locations
26
- G5Updatable::LocationsUpdater.new(foundation_client).update
34
+ def update_location
35
+ G5Updatable::LocationsUpdater.new(location_hash).update
27
36
  end
28
37
 
29
38
  def update_client
30
- G5Updatable::ClientUpdater.new(foundation_client).update
39
+ G5Updatable::ClientUpdater.new(client_hash).update
31
40
  end
32
41
 
33
- def foundation_client
34
- @foundation_client ||= G5FoundationClient::Client.find_by_uid(@client_uid)
42
+ def client_hash
43
+ G5Updatable::Fetcher.get_with_token(@client_uid)['client']
35
44
  end
36
45
 
46
+ def location_hash
47
+ G5Updatable::Fetcher.get_with_token(@location_uid)['location']
48
+ end
37
49
  end