spigot 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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