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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +5 -3
- data/Rakefile +1 -5
- data/examples/active_record.rb +18 -28
- data/examples/model.rb +4 -4
- data/examples/multi_resource.rb +61 -0
- data/lib/spigot.rb +11 -7
- data/lib/spigot/active_record.rb +28 -33
- data/lib/spigot/base.rb +5 -12
- data/lib/spigot/configuration.rb +1 -1
- data/lib/spigot/map/base.rb +4 -5
- data/lib/spigot/map/definition.rb +19 -9
- data/lib/spigot/map/option.rb +1 -13
- data/lib/spigot/map/resource.rb +11 -6
- data/lib/spigot/map/service.rb +5 -5
- data/lib/spigot/patch.rb +5 -5
- data/lib/spigot/proxy.rb +32 -20
- data/lib/spigot/record.rb +40 -13
- data/lib/spigot/translator.rb +27 -43
- data/lib/spigot/version.rb +2 -1
- data/script/active_record.rb +35 -0
- data/script/console.rb +19 -2
- data/spec/fixtures/data/active_user.rb +2 -4
- data/spec/fixtures/data/post.rb +1 -5
- data/spec/fixtures/data/user.rb +5 -5
- data/spec/fixtures/mappings/active_user_map.rb +11 -5
- data/spec/fixtures/mappings/post_map.rb +6 -18
- data/spec/fixtures/mappings/user_map.rb +1 -5
- data/spec/spec_helper.rb +13 -6
- data/spec/spigot/active_record_spec.rb +12 -5
- data/spec/spigot/base_spec.rb +2 -14
- data/spec/spigot/configuration_spec.rb +7 -7
- data/spec/spigot/map/base_spec.rb +12 -6
- data/spec/spigot/map/definition_spec.rb +51 -4
- data/spec/spigot/map/resource_spec.rb +4 -4
- data/spec/spigot/map/service_spec.rb +19 -14
- data/spec/spigot/patch_spec.rb +12 -0
- data/spec/spigot/proxy_spec.rb +17 -17
- data/spec/spigot/record_spec.rb +75 -4
- data/spec/spigot/spigot_spec.rb +9 -4
- data/spec/spigot/translator_spec.rb +86 -87
- data/spigot.gemspec +9 -10
- metadata +33 -47
- data/lib/spigot/config/spigot/github.yml +0 -7
- data/spec/support/active_record.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bbf49ff77ef26eb205af75ddcedcf4b7b980c213
|
4
|
+
data.tar.gz: 3dda020a7fa625e02bda396c46ac2575b1d3cdb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3caace51c1f63f2dd53d53d3a0a948513016f3b6735e186eea2089b6d005d4d49f40136360f82d393dcbba65c810af958551f5043922c7ebe2a53aadacf6a5c1
|
7
|
+
data.tar.gz: 3fd383bdd2948e1bb90e88c046b5a7a866027104c50b0f405333a679b3f13d3c1941977532971f9f4e7af9a03964ff8549c54a894978d239f47762808d0a8e40
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# Spigot
|
2
2
|
|
3
|
-
[](https://travis-ci.org/mwerner/spigot)
|
4
|
-
[](https://codeclimate.com/github/mwerner/spigot)
|
3
|
+
[](https://travis-ci.org/mwerner/spigot, "Travis CI")
|
4
|
+
[](https://codeclimate.com/github/mwerner/spigot, "Code Climate")
|
5
|
+
[](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(
|
36
|
+
User.find_or_create_by_api(params[:data])
|
35
37
|
|
36
38
|
Much better.
|
37
39
|
|
data/Rakefile
CHANGED
data/examples/active_record.rb
CHANGED
@@ -4,53 +4,43 @@ require 'net/http'
|
|
4
4
|
require 'uri'
|
5
5
|
|
6
6
|
ActiveRecord::Base.logger = Spigot.logger
|
7
|
-
|
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
|
9
|
+
class ActiveUser < ActiveRecord::Base
|
23
10
|
include Spigot::Base
|
24
11
|
end
|
25
12
|
|
26
|
-
Spigot.resource :
|
13
|
+
Spigot.resource :active_user do
|
27
14
|
id :github_id
|
15
|
+
name :name
|
28
16
|
login :username
|
29
|
-
|
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 =
|
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
|
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.
|
46
|
-
puts
|
47
|
-
puts
|
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 `
|
50
|
-
puts
|
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 "\
|
54
|
-
puts
|
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!"
|
data/examples/model.rb
CHANGED
@@ -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(:
|
55
|
+
new_by_api(github: api_data)
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
puts
|
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!"
|
data/lib/spigot.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
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
|
-
|
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
|
-
|
52
|
-
|
53
|
-
buffer.
|
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
|
data/lib/spigot/active_record.rb
CHANGED
@@ -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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
36
|
-
#
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
77
|
-
|
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
|
-
|
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
|
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
|
data/lib/spigot/base.rb
CHANGED
@@ -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)
|
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]
|
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
|
data/lib/spigot/configuration.rb
CHANGED
data/lib/spigot/map/base.rb
CHANGED
@@ -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.
|
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
|