spigot 0.2.2 → 0.3.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +5 -3
  4. data/Rakefile +1 -5
  5. data/examples/active_record.rb +18 -28
  6. data/examples/model.rb +4 -4
  7. data/examples/multi_resource.rb +61 -0
  8. data/lib/spigot.rb +11 -7
  9. data/lib/spigot/active_record.rb +28 -33
  10. data/lib/spigot/base.rb +5 -12
  11. data/lib/spigot/configuration.rb +1 -1
  12. data/lib/spigot/map/base.rb +4 -5
  13. data/lib/spigot/map/definition.rb +19 -9
  14. data/lib/spigot/map/option.rb +1 -13
  15. data/lib/spigot/map/resource.rb +11 -6
  16. data/lib/spigot/map/service.rb +5 -5
  17. data/lib/spigot/patch.rb +5 -5
  18. data/lib/spigot/proxy.rb +32 -20
  19. data/lib/spigot/record.rb +40 -13
  20. data/lib/spigot/translator.rb +27 -43
  21. data/lib/spigot/version.rb +2 -1
  22. data/script/active_record.rb +35 -0
  23. data/script/console.rb +19 -2
  24. data/spec/fixtures/data/active_user.rb +2 -4
  25. data/spec/fixtures/data/post.rb +1 -5
  26. data/spec/fixtures/data/user.rb +5 -5
  27. data/spec/fixtures/mappings/active_user_map.rb +11 -5
  28. data/spec/fixtures/mappings/post_map.rb +6 -18
  29. data/spec/fixtures/mappings/user_map.rb +1 -5
  30. data/spec/spec_helper.rb +13 -6
  31. data/spec/spigot/active_record_spec.rb +12 -5
  32. data/spec/spigot/base_spec.rb +2 -14
  33. data/spec/spigot/configuration_spec.rb +7 -7
  34. data/spec/spigot/map/base_spec.rb +12 -6
  35. data/spec/spigot/map/definition_spec.rb +51 -4
  36. data/spec/spigot/map/resource_spec.rb +4 -4
  37. data/spec/spigot/map/service_spec.rb +19 -14
  38. data/spec/spigot/patch_spec.rb +12 -0
  39. data/spec/spigot/proxy_spec.rb +17 -17
  40. data/spec/spigot/record_spec.rb +75 -4
  41. data/spec/spigot/spigot_spec.rb +9 -4
  42. data/spec/spigot/translator_spec.rb +86 -87
  43. data/spigot.gemspec +9 -10
  44. metadata +33 -47
  45. data/lib/spigot/config/spigot/github.yml +0 -7
  46. data/spec/support/active_record.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 43fb252116ef08da32e804dd2cd1a5bf1272f386
4
- data.tar.gz: eb143439496123d5318df58b43bb353855ed9f32
3
+ metadata.gz: bbf49ff77ef26eb205af75ddcedcf4b7b980c213
4
+ data.tar.gz: 3dda020a7fa625e02bda396c46ac2575b1d3cdb5
5
5
  SHA512:
6
- metadata.gz: 2b8c421ded53429d8569655cf014921af15656ba77479dd07e05bafde28df913022b5f591236552235980359598127b48ff234ebccc9d1ac7468c1164d8110b3
7
- data.tar.gz: b08b8e03fb47aa5d5a474aca6eaa08d166fbba299e77afbd73af6e8430bbe1e9ce35c41278d18b6536f911804572acc74b9062f87ce992eae6cdfd0a253d154b
6
+ metadata.gz: 3caace51c1f63f2dd53d53d3a0a948513016f3b6735e186eea2089b6d005d4d49f40136360f82d393dcbba65c810af958551f5043922c7ebe2a53aadacf6a5c1
7
+ data.tar.gz: 3fd383bdd2948e1bb90e88c046b5a7a866027104c50b0f405333a679b3f13d3c1941977532971f9f4e7af9a03964ff8549c54a894978d239f47762808d0a8e40
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  *.rbc
3
3
  .bundle
4
4
  .config
5
+ .gitconfig
5
6
  .yardoc
6
7
  .rspec
7
8
  .DS_Store
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Spigot
2
2
 
3
- [![Build Status](https://travis-ci.org/mwerner/spigot.png?branch=master)](https://travis-ci.org/mwerner/spigot)
4
- [![Code Climate](https://codeclimate.com/github/mwerner/spigot.png)](https://codeclimate.com/github/mwerner/spigot)
3
+ [![Build Status](https://travis-ci.org/mwerner/spigot.png?branch=master)](https://travis-ci.org/mwerner/spigot, "Travis CI")
4
+ [![Code Climate](https://codeclimate.com/github/mwerner/spigot.png)](https://codeclimate.com/github/mwerner/spigot, "Code Climate")
5
+ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/mwerner/spigot/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
5
6
 
6
7
  Spigot is an attempt to bring some sanity to consuming external API data. Without Spigot, you need
7
8
  to do this manual mapping at creation, such as:
@@ -28,10 +29,11 @@ to do this manual mapping at creation, such as:
28
29
  end
29
30
 
30
31
  This becomes particularly difficult as you start having multiple external sources for the same resource (ex: users from both twitter and facebook).
32
+
31
33
  Spigot uses a ruby api to map the data you receive to the columns of your database. As a result, you're
32
34
  able to convey a mapping of their structure into your attributes in a concise format. Afterwards, you can accomplish the above work in a simple statement:
33
35
 
34
- User.find_or_create_by_api(:github, params[:data])
36
+ User.find_or_create_by_api(params[:data])
35
37
 
36
38
  Much better.
37
39
 
data/Rakefile CHANGED
@@ -1,7 +1,3 @@
1
1
  require "bundler/gem_tasks"
2
-
3
- task :spec do
4
- system("rspec spec/")
5
- end
6
-
2
+ task(:spec){system("rspec spec/")}
7
3
  task :default => [:spec]
@@ -4,53 +4,43 @@ require 'net/http'
4
4
  require 'uri'
5
5
 
6
6
  ActiveRecord::Base.logger = Spigot.logger
7
- ActiveRecord::Base.establish_connection({
8
- :adapter => "sqlite3",
9
- :database => ':memory:'
10
- })
11
-
12
- ActiveRecord::Schema.define do
13
- self.verbose = false
14
- create_table :users, :force => true do |t|
15
- t.integer :github_id
16
- t.string :username
17
- t.string :image_url
18
- t.string :profile_url
19
- end
20
- end
7
+ require_relative '../script/active_record'
21
8
 
22
- class User < ActiveRecord::Base
9
+ class ActiveUser < ActiveRecord::Base
23
10
  include Spigot::Base
24
11
  end
25
12
 
26
- Spigot.resource :user do
13
+ Spigot.resource :active_user do
27
14
  id :github_id
15
+ name :name
28
16
  login :username
29
- avatar_url :image_url
30
- url :profile_url
17
+ gravatar_id :token
31
18
  options do
32
19
  primary_key :username
33
20
  end
34
21
  end
35
22
 
36
- profile_url = "https://api.github.com/users/mwerner"
23
+ profile_url = 'https://api.github.com/users/mwerner'
37
24
  puts "Making a request to an external API (Github): #{profile_url}"
38
25
  response = Net::HTTP.get_response URI.parse(profile_url)
39
26
  puts "Parse the response:\n `data = JSON.parse(response.body)`"
40
27
  data = JSON.parse(response.body)
41
28
 
42
- puts "\nIt Returned a whole bunch of data: "
43
- puts "#{data.inspect[0..100]}... etc, etc, etc (#{data.keys.length} more keys received)"
29
+ puts "\nIt returns a whole bunch of data: "
30
+ puts "#{data.inspect[0..100]} ... etc, etc, etc (#{data.keys.length} more keys received)"
44
31
 
45
- puts "\nWe don't want to use all of it. We can define a map on Spigot:"
46
- puts User.spigot.map.to_hash.inspect
47
- puts "Each key is an attribute received from the API, and the corresponding value is our column name."
32
+ puts "\nWe don't want to use all of it. Let's define a map on Spigot:"
33
+ puts ActiveUser.spigot.map.to_hash.inspect
34
+ puts 'Each key is an attribute received from the API, the corresponding value is our column name.'
48
35
 
49
- puts "\nWe define our primary key in the spigot `User` options, so we know how to check if the record already exists:"
50
- puts User.spigot.options.inspect
36
+ puts "\nWe define our primary key in the spigot `ActiveUser` options;"
37
+ puts 'now we know how to check if the record already exists:'
38
+ puts ActiveUser.spigot.options.inspect
51
39
 
40
+ puts "\nWe can create a new user with one line: `ActiveUser.find_or_create_by_api(data)`"
41
+ puts ActiveUser.find_or_create_by_api(data).inspect
52
42
 
53
- puts "\nWe can create a new user with one nice and easy line: `User.find_or_create_by_api(data)`"
54
- puts User.find_or_create_by_api(data).inspect
43
+ puts "\nThen running it again, we'll see Spigot returns the existing record."
44
+ puts ActiveUser.find_or_create_by_api(data).inspect
55
45
 
56
46
  puts "\nEnjoy!"
@@ -41,22 +41,22 @@ class User
41
41
 
42
42
  attr_reader :name, :username
43
43
 
44
- def initialize(params={})
44
+ def initialize(params = {})
45
45
  params.each_pair do |k, v|
46
46
  instance_variable_set("@#{k}".to_sym, v)
47
47
  end
48
48
  end
49
49
 
50
50
  def self.api_data
51
- { full_name: 'matthew', login: 'mwerner' }
51
+ { id: '9238475', full_name: 'matthew', login: 'mwerner' }
52
52
  end
53
53
 
54
54
  def self.build
55
- new_by_api(:github, api_data)
55
+ new_by_api(github: api_data)
56
56
  end
57
57
  end
58
58
 
59
- puts "Map Built:"
59
+ puts 'Map Built:'
60
60
  puts Spigot.config.map.to_hash
61
61
 
62
62
  user = User.build
@@ -0,0 +1,61 @@
1
+ require 'active_record'
2
+ require 'spigot'
3
+ require 'net/http'
4
+ require 'uri'
5
+
6
+ ActiveRecord::Base.logger = Spigot.logger
7
+ require_relative '../script/active_record'
8
+
9
+ class Event < ActiveRecord::Base
10
+ include Spigot::Base
11
+ end
12
+
13
+ class ActiveUser < ActiveRecord::Base
14
+ include Spigot::Base
15
+ end
16
+
17
+ Spigot.define do
18
+ resource :active_user do
19
+ id :github_id
20
+ name :name
21
+ login :username
22
+ gravatar_id :token
23
+ options do
24
+ primary_key :username
25
+ end
26
+ end
27
+
28
+ resource :event do
29
+ id :github_id
30
+ type :name
31
+ actor ActiveUser
32
+ options do
33
+ primary_key :id
34
+ end
35
+ end
36
+ end
37
+
38
+ events_url = 'https://api.github.com/events'
39
+
40
+ puts 'We can start off by making a request to get the latest github events'
41
+ response = Net::HTTP.get_response URI.parse(events_url)
42
+ puts "Parse the response:\n `data = JSON.parse(response.body)`"
43
+ event_data = JSON.parse(response.body)
44
+
45
+ puts "\nIt returns a whole bunch of data: "
46
+ puts "#{event_data.inspect[0..100]} ... etc, etc, etc\n\n"
47
+
48
+ puts 'Lets take the first three events and run them through spigot.'
49
+ puts 'We only want a few attributes: id, type and the associated user.'
50
+ puts Event.spigot.map.to_hash.inspect
51
+
52
+ puts "We'll assign a primary key in our options to make sure we don't duplicate events"
53
+ puts Event.spigot.options.inspect
54
+
55
+ puts "\nWe can parse all the events with one nice and easy line: `Event.spigot.find_or_create(data)`"
56
+ puts Event.spigot.find_or_create(event_data.first(3)).inspect
57
+
58
+ puts "\nSpigot has used the ActiveUser spigot map to create users for each event"
59
+ puts ActiveUser.all.map { |user| "#{user.id}: #{user.username}" }
60
+
61
+ puts "\nEnjoy!"
@@ -1,7 +1,9 @@
1
- require "spigot/version"
2
- require "spigot/errors"
3
- require "spigot/patch"
1
+ require 'spigot/version'
2
+ require 'spigot/errors'
3
+ require 'spigot/patch'
4
+ require 'logger'
4
5
 
6
+ # Parent module for Spigot
5
7
  module Spigot
6
8
  autoload :ActiveRecord, 'spigot/active_record'
7
9
  autoload :Base, 'spigot/base'
@@ -9,6 +11,7 @@ module Spigot
9
11
  autoload :Proxy, 'spigot/proxy'
10
12
  autoload :Record, 'spigot/record'
11
13
  autoload :Translator, 'spigot/translator'
14
+ # Spigot mapping and definition classes
12
15
  module Map
13
16
  autoload :Base, 'spigot/map/base'
14
17
  autoload :Definition, 'spigot/map/definition'
@@ -41,16 +44,17 @@ module Spigot
41
44
  Configuration.instance
42
45
  end
43
46
 
44
- ##=> Support
47
+ # Support
45
48
  def self.root
46
49
  File.expand_path('../..', __FILE__)
47
50
  end
48
51
 
49
52
  def self.logger
50
53
  @log ||= Spigot.config.logger || begin
51
- if buffer = Logger.new(STDOUT)
52
- buffer.level = ($0 == 'irb' ? Logger::DEBUG : Logger::INFO)
53
- buffer.formatter = proc{|severity, datetime, progname, msg| "#{msg}\n"}
54
+ buffer = Logger.new(STDOUT)
55
+ if buffer
56
+ buffer.level = ($PROGRAM_NAME == 'irb' ? Logger::DEBUG : Logger::INFO)
57
+ buffer.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" }
54
58
  end
55
59
  buffer
56
60
  end
@@ -1,58 +1,53 @@
1
1
  module Spigot
2
2
  module ActiveRecord
3
3
  module ClassMethods
4
-
5
4
  ## #find_by_api(params)
6
5
  # Build a query based on the defined map for this resource
7
6
  # to find a single matching record in the database
8
7
  #
9
8
  # @param params [Hash] Data as received from the api with optional service key
10
- def find_by_api(params={})
9
+ def find_by_api(params = {})
11
10
  find_all_by_api(params).first
12
11
  end
13
12
 
14
13
  ## #find_all_by_api(params)
15
14
  # Build a query based on the defined map for this resource
16
- # to find all matching records in the database
15
+ # to find all matching records in the database.
17
16
  #
18
17
  # @param params [Hash] Data as received from the api with optional service key
19
- def find_all_by_api(params={})
20
- service, data = Spigot::Map::Service.extract(params)
21
- find_by_translator Translator.new(self, service, data)
18
+ def find_all_by_api(params = {})
19
+ find_by_translator params_to_translator(params)
22
20
  end
23
21
 
24
22
  ## #create_by_api(params)
25
23
  # Insert mapped data into the calling model's table. Does not
26
- # do any checks on existing content already present in the database
24
+ # perform any checks on existing content already present in the database
27
25
  #
28
26
  # @param params [Hash] Data as received from the api with optional service key
29
- def create_by_api(params={})
30
- service, data = Spigot::Map::Service.extract(params)
31
- create_by_translator Translator.new(self, service, data)
27
+ def create_by_api(params = {})
28
+ create_by_translator params_to_translator(params)
32
29
  end
33
30
 
34
31
  ## #update_by_api(params)
35
- # Queries the database to find an existing record, based on the options
36
- # provided to spigot. If a record is found, it updates that record
37
- # with any new data received by the API
32
+ # Queries the database to find an existing record.
33
+ # If a record is found, it updates that record
34
+ # with any new formatted data received by the API
38
35
  #
39
36
  # @param params [Hash] Data as received from the api with optional service key
40
- def update_by_api(params={})
41
- service, data = Spigot::Map::Service.extract(params)
42
- babel = Translator.new(self, service, data)
37
+ def update_by_api(params = {})
38
+ babel = params_to_translator(params)
43
39
  record = find_by_translator(babel).first
44
40
  update_by_translator(babel, record) if record.present?
45
41
  end
46
42
 
47
43
  ## #find_or_create_by_api(params)
48
44
  # Queries the database to find an existing record. If that record is found
49
- # simply return it, otherwise create a new record and return it. This does
45
+ # simply return it, otherwise return a newly created record. This does
50
46
  # not update any existing record. If you want that, use `create_or_update_by_api`
51
47
  #
52
48
  # @param params [Hash] Data as received from the api with optional service key
53
- def find_or_create_by_api(params={})
54
- service, data = Spigot::Map::Service.extract(params)
55
- babel = Translator.new(self, service, data)
49
+ def find_or_create_by_api(params = {})
50
+ babel = params_to_translator(params)
56
51
  find_by_translator(babel).first || create_by_translator(babel)
57
52
  end
58
53
 
@@ -62,44 +57,44 @@ module Spigot
62
57
  # creates a new record and returns the newly created record.
63
58
  #
64
59
  # @param params [Hash] Data as received from the api with optional service key
65
- def create_or_update_by_api(params={})
66
- service, data = Spigot::Map::Service.extract(params)
67
- babel = Translator.new(self, service, data)
60
+ def create_or_update_by_api(params = {})
61
+ babel = params_to_translator(params)
68
62
  record = find_by_translator(babel).first
69
63
  record.present? ? update_by_translator(babel, record) : create_by_translator(babel)
70
64
  end
71
65
 
72
66
  private
73
67
 
68
+ def params_to_translator(params)
69
+ service, data = Spigot::Map::Service.extract(params)
70
+ Translator.new(self, service, data)
71
+ end
72
+
74
73
  def find_by_translator(translator)
75
74
  if invalid_primary_keys?(translator)
76
- raise Spigot::InvalidSchemaError, "The #{translator.primary_key} column does not exist on #{self.to_s}"
77
- end
78
-
79
- if translator.id.blank?
80
- Spigot.logger.warn " <Spigot::Warning> No #{translator.service} API data found at :#{translator.foreign_key}"
75
+ message = "The #{translator.primary_key} column does not exist on #{to_s}"
76
+ raise Spigot::InvalidSchemaError, message
81
77
  end
82
78
 
83
79
  return [] if translator.conditions.blank?
84
- self.where(translator.conditions)
80
+ where(translator.conditions)
85
81
  end
86
82
 
87
83
  def create_by_translator(translator)
88
- Record.create(self, translator.format)
84
+ Record.create(translator.service, self, translator.format)
89
85
  end
90
86
 
91
87
  def update_by_translator(translator, record)
92
- Record.update(self, record, translator.format)
88
+ Record.update(translator.service, self, record, translator.format)
93
89
  record
94
90
  end
95
91
 
96
92
  def invalid_primary_keys?(translator)
97
93
  [*translator.primary_key].each do |key|
98
- return true unless self.column_names.include?(key.to_s)
94
+ return true unless column_names.include?(key.to_s)
99
95
  end
100
96
  false
101
97
  end
102
98
  end
103
-
104
99
  end
105
100
  end
@@ -11,24 +11,17 @@ module Spigot
11
11
  # Instantiate a new object mapping the api data to the calling object's attributes
12
12
  #
13
13
  # @param params [Hash] Data as received from the api with optional service key
14
- def new_by_api(params)
15
- Record.instantiate(self, formatted_api_data(params))
16
- end
17
-
18
- # #self.formatted_api_data(params)
19
- # Create a Spigot::Translator for the given service and return the formatted data.
20
- #
21
- # @param params [Hash] Data as received from the api with optional service key
22
- def formatted_api_data(params={})
14
+ def new_by_api(params = {})
23
15
  service, data = Spigot::Map::Service.extract(params)
24
- Translator.new(self, service, data).format
16
+ translator = Translator.new(self, service, data)
17
+ Record.instantiate(service, self, translator.format)
25
18
  end
26
19
 
27
20
  # #self.spigot
28
21
  # Return a Spigot::Proxy that provides accessor methods to the spigot library
29
22
  #
30
- # @param service [Symbol] Service which pertains to the data being processed on the implementation
31
- def spigot(service=nil)
23
+ # @param service [Symbol] Data for service being processed on the implementation
24
+ def spigot(service = nil)
32
25
  Spigot::Proxy.new(self, service)
33
26
  end
34
27
  end
@@ -21,7 +21,7 @@ module Spigot
21
21
  end
22
22
 
23
23
  def reset
24
- @@defaults.each_pair{|k,v| self.send("#{k}=",v)}
24
+ @@defaults.each_pair { |k, v| send("#{k}=", v) }
25
25
  end
26
26
  end
27
27
  end
@@ -1,7 +1,6 @@
1
1
  module Spigot
2
2
  module Map
3
3
  class Base
4
-
5
4
  attr_reader :services
6
5
 
7
6
  def initialize
@@ -14,7 +13,7 @@ module Spigot
14
13
  end
15
14
 
16
15
  def update(name, service)
17
- @services.reject!{|s| s.name == name.to_s.underscore.to_sym}
16
+ @services.reject! { |s| s.name == name.to_s.underscore.to_sym }
18
17
  @services << service
19
18
  end
20
19
 
@@ -23,14 +22,14 @@ module Spigot
23
22
  end
24
23
 
25
24
  def service(name)
26
- services.detect{|service| service.name == name.to_s.underscore.to_sym}
25
+ services.find { |service| service.name == name.to_s.underscore.to_sym }
27
26
  end
28
27
 
29
28
  def to_hash
30
- hash = {};
29
+ hash = {}
31
30
  services.each do |service|
32
31
  service_map = {}
33
- service.resources.each{|resource| service_map.merge!(resource.to_hash) }
32
+ service.resources.each { |resource| service_map.merge!(resource.to_hash) }
34
33
  hash.merge!(service.name.to_sym => service_map)
35
34
  end
36
35
  hash