g5_updatable 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -34
  3. data/app/concerns/g5_updatable/belongs_to_location.rb +14 -0
  4. data/app/concerns/g5_updatable/first_class_properties.rb +25 -0
  5. data/app/concerns/g5_updatable/urn_as_parameter.rb +7 -0
  6. data/app/controllers/g5_updatable/feed_controller.rb +1 -1
  7. data/app/models/g5_updatable/client.rb +12 -0
  8. data/app/models/g5_updatable/integration_setting.rb +20 -0
  9. data/app/models/g5_updatable/location.rb +16 -0
  10. data/app/serializers/g5_updatable/location_serializer.rb +12 -0
  11. data/db/migrate/20140709222005_create_g5_updatable_clients_and_locations.rb +24 -0
  12. data/db/migrate/20141030211945_create_integration_setting.rb +18 -0
  13. data/lib/g5_updatable/client_feed_processor.rb +34 -12
  14. data/lib/g5_updatable/client_updater.rb +7 -16
  15. data/lib/g5_updatable/engine.rb +2 -13
  16. data/lib/g5_updatable/integration_settings_updater.rb +21 -0
  17. data/lib/g5_updatable/locations_updater.rb +14 -21
  18. data/lib/g5_updatable/rspec/factories.rb +19 -0
  19. data/lib/g5_updatable/rspec.rb +3 -0
  20. data/lib/g5_updatable/version.rb +1 -1
  21. data/lib/g5_updatable.rb +2 -1
  22. data/lib/generators/g5_updatable/install/USAGE +0 -3
  23. data/lib/generators/g5_updatable/install/install_generator.rb +0 -6
  24. data/lib/tasks/g5_updatable_tasks.rake +12 -4
  25. data/spec/concerns/g5_updatable/belongs_to_location_spec.rb +30 -0
  26. data/spec/dummy/app/models/favorite_food.rb +3 -0
  27. data/spec/dummy/config/database.sample.yml +9 -0
  28. data/spec/dummy/config/database.travis.yml +4 -0
  29. data/spec/dummy/db/migrate/20140709220627_drop_clients_and_locations.rb +10 -0
  30. data/spec/dummy/db/migrate/20140714225203_create_favorite_foods.rb +10 -0
  31. data/spec/dummy/db/schema.rb +39 -21
  32. data/spec/dummy/log/test.log +18589 -920
  33. data/spec/lib/g5_updatable/client_feed_processor_spec.rb +77 -22
  34. data/spec/lib/g5_updatable/client_updater_spec.rb +28 -41
  35. data/spec/lib/g5_updatable/integration_settings_updater_spec.rb +48 -0
  36. data/spec/lib/g5_updatable/locations_updater_spec.rb +29 -52
  37. data/spec/lib/generators/g5_updatable/install_generator_spec.rb +0 -12
  38. data/spec/models/g5_updatable/client_spec.rb +25 -0
  39. data/spec/models/g5_updatable/integration_setting_spec.rb +33 -0
  40. data/spec/models/g5_updatable/location_spec.rb +34 -0
  41. data/spec/serializers/g5_updatable/location_serializer_spec.rb +21 -0
  42. data/spec/spec_helper.rb +13 -5
  43. data/spec/support/shared_example_for_urn_as_parameter.rb +7 -0
  44. data/spec/support/shared_examples_for_first_class_properties_json.rb +29 -0
  45. metadata +104 -42
  46. data/lib/g5_updatable/feed_mapper.rb +0 -58
  47. data/lib/g5_updatable/g5_client.rb +0 -10
  48. data/lib/g5_updatable/g5_location.rb +0 -22
  49. data/lib/generators/g5_updatable/install/templates/g5_updatable.rb +0 -25
  50. data/spec/dummy/config/database.yml +0 -25
  51. data/spec/dummy/config/initializers/g5_updatable.rb +0 -4
  52. data/spec/dummy/db/development.sqlite3 +0 -0
  53. data/spec/dummy/log/development.log +0 -6434
  54. data/spec/dummy/spec/fabricators/client_fabricator.rb +0 -6
  55. data/spec/dummy/spec/fabricators/location_fabricator.rb +0 -9
  56. data/spec/dummy/spec/models/client_spec.rb +0 -0
  57. data/spec/dummy/spec/models/location_spec.rb +0 -0
  58. data/spec/dummy/spec/support/client_feed.html +0 -97
  59. data/spec/dummy/spec/support/updated_client_feed.html +0 -148
  60. data/spec/fabricators/client_fabricator.rb +0 -6
  61. data/spec/fabricators/location_fabricator.rb +0 -9
  62. data/spec/lib/g5_updatable/feed_mapper_spec.rb +0 -119
  63. data/spec/lib/tmp/config/initializers/g5_updatable.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bde0a7d86b404b9ac963b31247542ba845666eeb
4
- data.tar.gz: d0b7a782024e35fd093883883979078370ef1db6
3
+ metadata.gz: 3a07cbb7571a2a542b0ee33d002767be1a2a55f7
4
+ data.tar.gz: 03a45dc270a2144190f1b8d75c9790639e3dfea7
5
5
  SHA512:
6
- metadata.gz: a8024e7f5fd6aa37805eac7b87e11d4cffd2f7802286de63709761eeac71ff86056ebdf517f4ba13b08b3e18e0e538d7ee819d6f5717013e16db7c1ae28192f8
7
- data.tar.gz: 76c110e7d08fa1ea6c1608e081ddb8673ba0743d432d3f89ba7d1596bb0991f8ffcc30a3e40813511d8d5d7704c64a404872081b85a804c314319d1ed00f49cb
6
+ metadata.gz: 1eddbf5326ca2ed281892b39457f5b9c79b3dccb2689c898aeeca03107dbcbe5cee213f6cebbd922d316640445dba98d0ddcb0612137aee606c212a8450db8b6
7
+ data.tar.gz: 55b69f64376dae0bc537a0b62f0e17b4ed1e754fac7809eb50fff6f8910a79153e2fcdcb1bf349ead4ff13a9a8ce60b69b984eeb8178a5b993c8fd6b862f5dad
data/README.md CHANGED
@@ -6,6 +6,10 @@
6
6
  G5 Updatable provides a solution for automatic updates of client and location
7
7
  data when modified or created in the G5 Hub.
8
8
 
9
+ ## Requirements
10
+
11
+ G5 Updatable makes use of PostgrSQL's `json` field type, and so only supports implementing apps that also use PostgreSQL.
12
+
9
13
  ## Installation
10
14
 
11
15
  1. Add this line to your application's Gemfile:
@@ -19,56 +23,53 @@ data when modified or created in the G5 Hub.
19
23
  ```console
20
24
  bundle
21
25
  ```
22
-
23
26
  3. Run the generator.
24
27
 
25
28
  ```ruby
26
29
  rails g g5_updatable:install
27
30
  ```
28
31
 
29
- This creates an initilizer at config/initializers/g5_updatable.rb,
30
- and mounts the engine at `/g5_updatable`.
32
+ This mounts the engine at `/g5_updatable`.
33
+
34
+ 3. And copy the engine's migrations to your application:
35
+
36
+ ```console
37
+ rake g5_updatable:install:migrations
38
+ ```
39
+ 3. Optional: load all of G5-Hub's data into your database
40
+
41
+ ```console
42
+ rake g5_updatable:load_all
43
+ ```
44
+ Note, all of the G5_AUTH env variables need to be set for this to work.
45
+
46
+ ## Usage
47
+
48
+ G5 Updatable exposes a POST route at `/g5_updatable/update` that accepts a
49
+ `client_uid` parameter (the URL for the client's detail page within the G5
50
+ Hub). When the route is hit, it will update/create Location and Client.
31
51
 
32
- ## Configuration
52
+ See the [G5-Hub](https://github.com/G5/g5-hub/blob/master/lib/webhook_poster.rb) webhook logic for further info.
33
53
 
34
- You can configure options within the generated initializer.
54
+ ### Location Association
55
+
56
+ You will likely have models in your own application that are associated with a Location. A module is available to include in your related models to support this association. Assuming your model has a `location_uid` string field, you can use the module as follows:
35
57
 
36
58
  ```ruby
37
- # config/initilizers/g5_updatable.rb
38
- G5Updatable.setup do |config|
39
- # Default is nil. Most likely will be coming in via the hub urn parameter.
40
- # dentifier of the client (urn)
41
- config.client_identifier = #string
42
-
43
- # Default is ""http://g5-hub.herokuapp.com/clients/". Base path to the G5 Hub
44
- config.feed_endpoint = #string
45
-
46
- # default is true. When set to true, existing locations in your app will be
47
- # updated with any changes made to the hub. If set to false, existing locations
48
- # will be skipped and only newly added locations will be created.
49
- config.update_locations = #boolean
50
-
51
- # default is false. When set to true, client data will update.
52
- config.update_client = #boolean
53
-
54
- # default is [:name]. A whitlist of parameters to create/update on the model
55
- config.location_parameters = #array of symbols
56
-
57
- # default is [:name]. A whitlist of parameters to update on the model
58
- config.client_parameters = #array of symbols
59
+ class Foo < ActiveRecord::Base
60
+ include G5Updatable::BelongsToLocation
59
61
  end
60
62
  ```
61
63
 
62
- ## Usage
64
+ It provides a `#location` method that will fetch the correct location.
63
65
 
64
- G5 Updatable exposes a POST route at `/g5_updatable/update` that accepts a urn
65
- parameter (client identifier within the hub). When the route is
66
- hit, it will update/create Location and Client data based on the configuration.
66
+ ### Spec Helpers
67
67
 
68
- G5 Updatable assumes your app has a Location model with a minimum of a urn and
69
- name column.
68
+ The engine provides a helper files that can be included in your project to bring in some testing support. Currently this is limited to (some factory definitions)[https://github.com/G5/g5_updatable/blob/active-record-up-in-here/lib/g5_updatable/rspec/factories.rb]. In rspec you can add the following line to your `spec/spec_helper.rb`:
70
69
 
71
- See the [G5-Hub](https://github.com/G5/g5_hub/lib/webhook_poster.rb) webhook logic for further info.
70
+ ```ruby
71
+ require 'g5_updatable/rspec'
72
+ ```
72
73
 
73
74
  ## Authors
74
75
 
@@ -88,6 +89,16 @@ If you find bugs, have feature requests or questions, please
88
89
 
89
90
  ## Specs
90
91
 
92
+ The `database.yml` for the dummy app must be created and modified to match your
93
+ PostgreSQL configuration. If you are using the [G5 Orion
94
+ Vagrant](https://github.com/G5/g5-orion-vagrant) image, the sample file should
95
+ just work. You can copy it into place with:
96
+ ```bash
97
+ $ cp spec/dummy/config/database.sample.yml spec/dummy/config/database.yml
98
+ ```
99
+
100
+ Run specs via `rspec` with:
101
+
91
102
  ```bash
92
103
  $ rspec spec
93
104
  ```
@@ -0,0 +1,14 @@
1
+ module G5Updatable::BelongsToLocation
2
+ extend ActiveSupport::Concern
3
+
4
+ def location_uid=(location_uid)
5
+ @location = nil
6
+ write_attribute(:location_uid, location_uid)
7
+ end
8
+
9
+ def location
10
+ @location ||= G5Updatable::Location.find_by_uid!(location_uid)
11
+ rescue ActiveRecord::RecordNotFound
12
+ raise ActiveRecord::RecordNotFound.new("Can't find a location for uid '#{location_uid}'")
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ module G5Updatable::FirstClassProperties
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ after_initialize :define_methods_for_properties
6
+ end
7
+
8
+ def properties=(hash)
9
+ write_attribute("properties", hash)
10
+ define_methods_for_properties
11
+ end
12
+
13
+ protected
14
+
15
+ def define_methods_for_properties
16
+ return unless properties.present?
17
+
18
+ properties.each do |key, value|
19
+ next if respond_to?(key)
20
+ define_singleton_method(key) do
21
+ properties[key.to_s] if properties.present?
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ module G5Updatable::UrnAsParameter
2
+ extend ActiveSupport::Concern
3
+
4
+ def to_param
5
+ urn
6
+ end
7
+ end
@@ -3,7 +3,7 @@ class G5Updatable::FeedController < ApplicationController
3
3
  skip_before_filter :verify_authenticity_token
4
4
 
5
5
  def update
6
- G5Updatable::ClientFeedProcessor.new(params[:urn]).work
6
+ G5Updatable::ClientFeedProcessor.new(params[:client_uid]).work
7
7
  render json: {}, status: :ok
8
8
  end
9
9
  end
@@ -0,0 +1,12 @@
1
+ module G5Updatable
2
+ class Client < ActiveRecord::Base
3
+ include G5Updatable::FirstClassProperties
4
+ include G5Updatable::UrnAsParameter
5
+
6
+ validates :uid, :urn, presence: true
7
+
8
+ def locations
9
+ G5Updatable::Location.where(client_uid: self.uid)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ module G5Updatable
2
+ class IntegrationSetting < ActiveRecord::Base
3
+ include G5Updatable::FirstClassProperties
4
+ include G5Updatable::UrnAsParameter
5
+
6
+ validates :uid, presence: true
7
+ validates :location_uid, presence: true
8
+
9
+ INVENTORY = 'inventory'
10
+ LEAD = 'lead'
11
+
12
+ scope :by_vendor_action, -> (vendor_action) { where(vendor_action: vendor_action) }
13
+ scope :by_inventory, -> { by_vendor_action(INVENTORY) }
14
+ scope :by_lead, -> { by_vendor_action(LEAD) }
15
+
16
+ def location
17
+ @client ||= G5Updatable::Location.find_by_uid(location_uid)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ module G5Updatable
2
+ class Location < ActiveRecord::Base
3
+ include G5Updatable::FirstClassProperties
4
+ include G5Updatable::UrnAsParameter
5
+
6
+ validates :uid, :urn, :client_uid, presence: true
7
+
8
+ def client
9
+ @client ||= G5Updatable::Client.find_by_uid(client_uid)
10
+ end
11
+
12
+ def integration_settings
13
+ G5Updatable::IntegrationSetting.where(location_uid: self.uid)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ class G5Updatable::LocationSerializer < ActiveModel::Serializer
2
+ attributes :uid, :urn, :client_uid
3
+
4
+ def filter(keys)
5
+ object.properties.each do |name, value|
6
+ keys.push(name.to_sym)
7
+ define_singleton_method(name.to_sym) { value }
8
+ end
9
+
10
+ keys
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ class CreateG5UpdatableClientsAndLocations < ActiveRecord::Migration
2
+ def change
3
+ create_table :g5_updatable_clients do |t|
4
+ t.string :uid
5
+ t.string :urn
6
+ t.json :properties
7
+
8
+ t.timestamps
9
+ end
10
+ add_index :g5_updatable_clients, :uid
11
+ add_index :g5_updatable_clients, :urn
12
+
13
+ create_table :g5_updatable_locations do |t|
14
+ t.string :uid
15
+ t.string :urn
16
+ t.string :client_uid
17
+ t.json :properties
18
+
19
+ t.timestamps
20
+ end
21
+ add_index :g5_updatable_locations, :uid
22
+ add_index :g5_updatable_locations, :urn
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ class CreateIntegrationSetting < ActiveRecord::Migration
2
+ def change
3
+ create_table :g5_updatable_integration_settings do |t|
4
+ t.string :uid
5
+ t.string :urn
6
+ t.string :location_uid
7
+ t.string :vendor_action
8
+ t.integer :job_frequency_in_minutes
9
+ t.json :properties
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :g5_updatable_integration_settings, :urn
14
+ add_index :g5_updatable_integration_settings, :uid
15
+ add_index :g5_updatable_integration_settings, :vendor_action
16
+ add_index :g5_updatable_integration_settings, [:location_uid, :vendor_action], name: :g5_u_is_loc_action
17
+ end
18
+ end
@@ -1,22 +1,44 @@
1
- require "g5_updatable/feed_mapper"
2
- require "g5_updatable/client_updater"
3
- require "g5_updatable/locations_updater"
4
-
5
1
  class G5Updatable::ClientFeedProcessor
6
- def initialize(urn=nil)
7
- @urn = urn || G5Updatable.client_identifier
2
+ attr_reader :client_uid
3
+
4
+ def initialize(client_uid=nil)
5
+ @client_uid = client_uid || ENV["CLIENT_UID"]
6
+ raise "A client_uid must be either passed in or configured!" if @client_uid.blank?
7
+ end
8
+
9
+ 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 }
13
+ end
8
14
  end
9
15
 
10
16
  def work
11
- if @urn
12
- G5Updatable::ClientUpdater.new(feed_mapper.client).update
13
- G5Updatable::LocationsUpdater.new(feed_mapper.locations).update
17
+ client = update_client
18
+ update_locations
19
+ update_integrations
20
+
21
+ client
22
+ end
23
+
24
+ private
25
+
26
+ def update_integrations
27
+ foundation_client.locations.each do |location|
28
+ G5Updatable::IntegrationSettingsUpdater.new(location.integration_settings).update
14
29
  end
15
30
  end
16
31
 
17
- private
32
+ def update_locations
33
+ G5Updatable::LocationsUpdater.new(foundation_client.locations).update
34
+ end
35
+
36
+ def update_client
37
+ G5Updatable::ClientUpdater.new(foundation_client).update
38
+ end
18
39
 
19
- def feed_mapper
20
- @feed_mapper ||= G5Updatable::FeedMapper.new(@urn)
40
+ def foundation_client
41
+ @foundation_client ||= G5FoundationClient::Client.find_by_uid(@client_uid)
21
42
  end
43
+
22
44
  end
@@ -4,22 +4,13 @@ class G5Updatable::ClientUpdater
4
4
  end
5
5
 
6
6
  def update
7
- return unless G5Updatable.update_client && client
7
+ attributes = @g5_client.client_hash.dup
8
+ attributes.delete(:locations)
9
+ uid = attributes[:uid]
10
+ urn = attributes[:urn]
8
11
 
9
- G5Updatable.client_parameters.each do |parameter|
10
- client.send("#{parameter}=", @g5_client.send(parameter))
11
- end
12
-
13
- client.save
14
- end
15
-
16
- private
17
-
18
- def client
19
- @client ||= Client.find_by(uid: client_uid) if client_uid
20
- end
21
-
22
- def client_uid
23
- "#{G5Updatable.feed_endpoint}#{G5Updatable.client_identifier}"
12
+ G5Updatable::Client.
13
+ find_or_initialize_by(uid: uid).
14
+ update_attributes!(urn: urn, properties: attributes)
24
15
  end
25
16
  end
@@ -2,26 +2,15 @@ module G5Updatable
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace G5Updatable
4
4
 
5
+ config.autoload_paths << G5Updatable::Engine.root.join("lib")
6
+
5
7
  config.generators do |g|
6
8
  g.test_framework :rspec
7
- g.fixture_replacement :fabrication, dir: 'spec/fabricators'
8
9
  g.assets false
9
10
  g.helper false
10
11
  end
11
12
  end
12
13
 
13
- class << self
14
- mattr_accessor :client_identifier, :feed_endpoint, :location_parameters,
15
- :client_parameters, :update_client, :update_locations
16
-
17
- self.client_identifier = nil
18
- self.feed_endpoint = "http://g5-hub.herokuapp.com/clients/"
19
- self.location_parameters = [:name]
20
- self.client_parameters = [:name]
21
- self.update_client = false
22
- self.update_locations = true
23
- end
24
-
25
14
  def self.setup(&block)
26
15
  yield self
27
16
  end
@@ -0,0 +1,21 @@
1
+ class G5Updatable::IntegrationSettingsUpdater
2
+ def initialize(g5_integration_settings)
3
+ @g5_integration_settings = g5_integration_settings
4
+ end
5
+
6
+ def update
7
+ @g5_integration_settings.each do |g5_integration_setting|
8
+ attributes = g5_integration_setting.integration_setting_hash.dup
9
+
10
+ G5Updatable::IntegrationSetting.
11
+ find_or_initialize_by(uid: attributes[:uid]).
12
+ update_attributes!(
13
+ urn: attributes[:urn],
14
+ location_uid: attributes[:location_uid],
15
+ vendor_action: attributes[:vendor_action],
16
+ job_frequency_in_minutes: attributes[:job_frequency_in_minutes],
17
+ properties: attributes
18
+ )
19
+ end
20
+ end
21
+ end
@@ -1,29 +1,22 @@
1
1
  class G5Updatable::LocationsUpdater
2
- def initialize(locations)
3
- @locations = locations
2
+ def initialize(g5_locations)
3
+ @g5_locations = g5_locations
4
4
  end
5
5
 
6
6
  def update
7
- @locations.each { |location| process(location) unless skip?(location) }
8
- end
9
-
10
- private
7
+ @g5_locations.each do |g5_location|
8
+ attributes = g5_location.location_hash.dup
9
+ uid = attributes[:uid]
10
+ urn = attributes[:urn]
11
+ client_uid = attributes[:client_uid]
11
12
 
12
- def process(g5_location)
13
- location = Location.find_or_initialize_by(urn: urn_for(g5_location))
14
-
15
- G5Updatable.location_parameters.each do |parameter|
16
- location.send("#{parameter}=", g5_location.send(parameter))
13
+ G5Updatable::Location.
14
+ find_or_initialize_by(uid: uid).
15
+ update_attributes!(
16
+ urn: urn,
17
+ client_uid: client_uid,
18
+ properties: attributes
19
+ )
17
20
  end
18
-
19
- location.save
20
- end
21
-
22
- def skip?(location)
23
- Location.exists?(urn: urn_for(location)) && !G5Updatable.update_locations
24
- end
25
-
26
- def urn_for(location)
27
- location.uid.to_s.split("/").last
28
21
  end
29
22
  end