spigot 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +25 -32
  4. data/Rakefile +6 -0
  5. data/examples/active_record.rb +18 -15
  6. data/examples/model.rb +38 -7
  7. data/lib/spigot.rb +18 -6
  8. data/lib/spigot/configuration.rb +4 -5
  9. data/lib/spigot/map/base.rb +44 -0
  10. data/lib/spigot/map/definition.rb +69 -0
  11. data/lib/spigot/map/option.rb +27 -0
  12. data/lib/spigot/map/resource.rb +37 -0
  13. data/lib/spigot/map/service.rb +43 -0
  14. data/lib/spigot/patch.rb +1 -1
  15. data/lib/spigot/proxy.rb +3 -2
  16. data/lib/spigot/translator.rb +22 -71
  17. data/lib/spigot/version.rb +1 -1
  18. data/script/console.rb +26 -20
  19. data/spec/fixtures/data/active_user.rb +17 -0
  20. data/spec/fixtures/data/post.rb +15 -0
  21. data/spec/fixtures/data/user.rb +41 -0
  22. data/spec/fixtures/mappings/active_user_map.rb +58 -22
  23. data/spec/fixtures/mappings/post_map.rb +10 -10
  24. data/spec/fixtures/mappings/user_map.rb +73 -29
  25. data/spec/spec_helper.rb +3 -12
  26. data/spec/spigot/active_record_spec.rb +34 -26
  27. data/spec/spigot/base_spec.rb +32 -1
  28. data/spec/spigot/configuration_spec.rb +0 -27
  29. data/spec/spigot/map/base_spec.rb +70 -0
  30. data/spec/spigot/map/definition_spec.rb +45 -0
  31. data/spec/spigot/map/resource_spec.rb +57 -0
  32. data/spec/spigot/map/service_spec.rb +88 -0
  33. data/spec/spigot/translator_spec.rb +110 -113
  34. data/spigot.gemspec +3 -2
  35. metadata +43 -20
  36. data/examples/.DS_Store +0 -0
  37. data/lib/.DS_Store +0 -0
  38. data/lib/spigot/config/.DS_Store +0 -0
  39. data/spec/.DS_Store +0 -0
  40. data/spec/fixtures/.DS_Store +0 -0
  41. data/spec/fixtures/api_data.rb +0 -46
  42. data/spec/spigot/factory_spec.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c6e0174fc401969ff57755d4eb751c1ebb3bd5c5
4
- data.tar.gz: 2844483e9860825c20e4e6b6d255ffe80586843c
3
+ metadata.gz: 78085ef374e7bfcf77dd307ab4d659152baddaf0
4
+ data.tar.gz: 8852de5e0d8a2e3ad3ea6d2ba467b61483617be3
5
5
  SHA512:
6
- metadata.gz: 6edf56a3600624852a424f314faf8a1b092eb77a3e216e894964972a081a86348033f52f485540a0970a3c006683a91e7ebfcdba39a5ab9b6c489e08dd34d7ee
7
- data.tar.gz: a88764f01212671cf27efdcf1d12218ceec4915b49c767c17b2b1b8560cabefc7ddca0e9ea7ab93686fe1d36f95d9f035cb46c793bd7077d0181eb57094cfc89
6
+ metadata.gz: 0b71f04cd9d4677e6a090cf59460b6ef0619b88cddb4f4fd9ce2f8f890b4e93ddf4288def1ccd15205675e0f4b924e85e78b3bbea25f767b68f17cc17ab69e84
7
+ data.tar.gz: 2acb389f90be427885ee81c1159d6925b32344d3bbd8e148aa18232052182e4bf28b59f8a48ed14c9b12af3c32be3912466cb7c109ea1c91bdf5c2d4ea4f3f10
data/.gitignore CHANGED
@@ -4,6 +4,7 @@
4
4
  .config
5
5
  .yardoc
6
6
  .rspec
7
+ .DS_Store
7
8
  Gemfile.lock
8
9
  InstalledFiles
9
10
  _yardoc
data/README.md CHANGED
@@ -1,48 +1,41 @@
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)
5
+
3
6
  Spigot is an attempt to bring some sanity to consuming external API data. Without Spigot, you need
4
7
  to do this manual mapping at creation, such as:
5
8
 
6
- Pull.where(number: pull.number).first_or_initialize.tap do |t|
7
- t.title = pull.title
8
- t.body = pull.body
9
- t.url = pull._links.html.href
10
- t.head_ref = pull.head.ref
11
- t.head_sha = pull.head.sha
12
- t.base_ref = pull.base.ref
13
- t.base_sha = pull.base.sha
14
- t.save
15
- end
16
-
17
- Spigot reads config files in an expected format to map the data you receive to the columns of your database.
18
- This becomes particularly difficult as you start having multiple sources for the same resource (ex: users).
9
+ if params[:data].present?
10
+ data = params[:data]
11
+ record = User.where(external_id: data[:id]).first
19
12
 
20
- Spigot is able to do the translation for you and put the API data into a language your implementation understands.
21
- This leaves you with a simple statement to accomplish the same as above:
13
+ if record.nil?
14
+ url = "https://github.com/#{data[:login]}"
22
15
 
23
- Pull.find_or_create_by_api(:github, pull)
16
+ user = User.new({
17
+ name: data[:first_name],
18
+ email: data[:email_address],
19
+ url: url
20
+ })
24
21
 
25
- Much better. [Read More](https://github.com/mwerner/spigot/wiki)
22
+ if data[:profile].present?
23
+ user.bio = data[:profile][:text]
24
+ end
26
25
 
27
- ## Example
28
-
29
- # Our Model
30
- class User < ActiveRecord::Base
31
- include Spigot::Base
26
+ user.save!
27
+ end
32
28
  end
33
29
 
34
- # Api Data Received
35
- data = JSON.parse("{\"full_name\":\"Dean Martin\",\"login\":\"dino@amore.io\",\"token\":\"abc123\"}")
30
+ This becomes particularly difficult as you start having multiple external sources for the same resource (ex: users from both twitter and facebook).
31
+ Spigot uses a ruby api to map the data you receive to the columns of your database. As a result, you're
32
+ 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
+
34
+ User.find_or_create_by_api(:github, params[:data])
36
35
 
37
- # Spigot yaml file to map this data correctly
38
- user:
39
- full_name: name
40
- login: email
41
- token: auth
36
+ Much better.
42
37
 
43
- # Usage
44
- User.find_or_create_by_api(:github, data).inspect
45
- #=> #<User id: 1, name: "Dean Martin", email: "dino@amore.io", token: "abc123">
38
+ [Read More](http://mwerner.github.io/spigot/)
46
39
 
47
40
  ## Installation
48
41
 
data/Rakefile CHANGED
@@ -1 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ task :spec do
4
+ system("rspec spec/")
5
+ end
6
+
7
+ task :default => [:spec]
@@ -23,28 +23,31 @@ class User < ActiveRecord::Base
23
23
  include Spigot::Base
24
24
  end
25
25
 
26
- Spigot.configure do |config|
27
- config.translations = {'user' => {
28
- 'id' => 'github_id',
29
- 'login' => 'username',
30
- 'avatar_url' => 'image_url',
31
- 'url' => 'profile_url',
32
- 'spigot' => {
33
- 'primary_key' => 'username'
34
- }
35
- }}
26
+ Spigot.define do
27
+ service :github do
28
+ resource :user do
29
+ id :github_id
30
+ login :username
31
+ avatar_url :image_url
32
+ url :profile_url
33
+ options do
34
+ primary_key :username
35
+ end
36
+ end
37
+ end
36
38
  end
37
39
 
38
- puts "Making a request to an external API (Github)"
39
- response = Net::HTTP.get_response URI.parse("https://api.github.com/users/mwerner")
40
- puts "Parse the response: `data = JSON.parse(response.body)`"
40
+ profile_url = "https://api.github.com/users/mwerner"
41
+ puts "Making a request to an external API (Github): #{profile_url}"
42
+ response = Net::HTTP.get_response URI.parse(profile_url)
43
+ puts "Parse the response:\n `data = JSON.parse(response.body)`"
41
44
  data = JSON.parse(response.body)
42
45
 
43
- puts "\nReceived a whole bunch of data: "
46
+ puts "\nIt Returned a whole bunch of data: "
44
47
  puts "#{data.inspect[0..100]}... etc, etc, etc (#{data.keys.length} more keys received)"
45
48
 
46
49
  puts "\nWe don't want to use all of it. We can define a map on Spigot:"
47
- puts User.spigot(:github).map.inspect
50
+ puts User.spigot(:github).map.to_hash.inspect
48
51
  puts "Each key is an attribute received from the API, and the corresponding value is our column name."
49
52
 
50
53
  puts "\nWe define our primary key in the spigot `User` options, so we know how to check if the record already exists:"
@@ -1,12 +1,39 @@
1
1
  require 'spigot'
2
2
 
3
- Spigot.configure do |config|
4
- config.translations = {
5
- 'user' => {
6
- 'full_name' => 'name',
7
- 'login' => 'username'
8
- }
9
- }
3
+ Spigot.define do
4
+ service :twitter do
5
+ resource :user do
6
+ id :twitter_id
7
+ name :name
8
+ username :username
9
+ end
10
+ end
11
+ end
12
+
13
+ Spigot.define do
14
+ service :github do
15
+ resource :user do
16
+ id :github_id
17
+ full_name :name
18
+ login :username
19
+ contact do
20
+ address :address
21
+ telephone do
22
+ work :work_phone
23
+ home :home_phone
24
+ end
25
+ url :homepage do |value|
26
+ "https://github.com/#{value}"
27
+ end
28
+ end
29
+ end
30
+
31
+ resource :pull_request do
32
+ id :id
33
+ title :title
34
+ body :body
35
+ end
36
+ end
10
37
  end
11
38
 
12
39
  class User
@@ -29,6 +56,10 @@ class User
29
56
  end
30
57
  end
31
58
 
59
+ puts "Map Built:"
60
+ puts Spigot.config.map.to_hash
61
+
32
62
  user = User.build
63
+ puts "\nUser Parsed:"
33
64
  puts user.name
34
65
  puts user.inspect
@@ -3,21 +3,33 @@ require "spigot/errors"
3
3
  require "spigot/patch"
4
4
 
5
5
  module Spigot
6
- autoload :Configuration, 'spigot/configuration'
7
- autoload :Translator, 'spigot/translator'
8
- autoload :Record, 'spigot/record'
9
- autoload :Base, 'spigot/base'
10
6
  autoload :ActiveRecord, 'spigot/active_record'
7
+ autoload :Base, 'spigot/base'
8
+ autoload :Configuration, 'spigot/configuration'
11
9
  autoload :Proxy, 'spigot/proxy'
10
+ autoload :Record, 'spigot/record'
11
+ autoload :Translator, 'spigot/translator'
12
+ module Map
13
+ autoload :Base, 'spigot/map/base'
14
+ autoload :Definition, 'spigot/map/definition'
15
+ autoload :Option, 'spigot/map/option'
16
+ autoload :Resource, 'spigot/map/resource'
17
+ autoload :Service, 'spigot/map/service'
18
+ end
12
19
 
13
- def self.config
14
- Configuration.instance
20
+ def self.define(&block)
21
+ (config.map || Spigot::Map::Base.new).define(&block)
15
22
  end
16
23
 
17
24
  def self.configure
18
25
  yield config
19
26
  end
20
27
 
28
+ def self.config
29
+ Configuration.instance
30
+ end
31
+
32
+ ##=> Support
21
33
  def self.root
22
34
  File.expand_path('../..', __FILE__)
23
35
  end
@@ -4,13 +4,12 @@ module Spigot
4
4
  class Configuration
5
5
  include Singleton
6
6
 
7
- attr_accessor :path, :translations, :options_key, :logger
7
+ attr_accessor :options_key, :logger, :map
8
8
 
9
9
  @@defaults = {
10
- path: 'config/spigot',
11
- translations: nil,
12
- options_key: 'spigot',
13
- logger: nil
10
+ options_key: 'options',
11
+ logger: nil,
12
+ map: nil
14
13
  }
15
14
 
16
15
  def self.defaults
@@ -0,0 +1,44 @@
1
+ module Spigot
2
+ module Map
3
+ class Base
4
+
5
+ attr_reader :services
6
+
7
+ def initialize
8
+ @services = []
9
+ Spigot.config.map = self
10
+ end
11
+
12
+ def define(&block)
13
+ Spigot::Map::Service.class_eval(&block) if block_given?
14
+ end
15
+
16
+ def update(name, service)
17
+ @services.reject!{|s| s.name == name.to_s.underscore.to_sym}
18
+ @services << service
19
+ end
20
+
21
+ def reset
22
+ @services = []
23
+ end
24
+
25
+ def service(name)
26
+ services.detect{|service| service.name == name.to_s.underscore.to_sym}
27
+ end
28
+
29
+ def to_hash
30
+ hash = {};
31
+ services.each do |service|
32
+ service_map = {}
33
+ service.resources.each{|resource| service_map.merge!(resource.to_hash) }
34
+ hash.merge!(service.name.to_sym => service_map)
35
+ end
36
+ hash
37
+ end
38
+
39
+ def inspect
40
+ "#<Spigot::Map::Base #{to_hash.to_s}>"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,69 @@
1
+ module Spigot
2
+ module Map
3
+ class Definition
4
+
5
+ def initialize(name, args=nil, parent=nil, &block)
6
+ @name = name
7
+ @value = args
8
+ @children = []
9
+ self.instance_eval(&block) if block_given?
10
+ @parse = block unless @children.any?
11
+ end
12
+
13
+ def self.define(resource, name, value=nil, &block)
14
+ definition = new(name, value, &block)
15
+ resource.append definition
16
+ definition
17
+ end
18
+
19
+ def parse(data)
20
+ return {} if data.nil?
21
+
22
+ data.default_proc = proc{|h, k| h.key?(k.to_s) ? h[k.to_s] : nil} if data.is_a?(Hash)
23
+ if @children.empty?
24
+ value = @parse ? @parse.call(data[@name]) : data[@name]
25
+ return { @value.to_sym => value }
26
+ end
27
+
28
+ if data[@name].is_a?(Array)
29
+ set = []
30
+ data[@name].each do |element|
31
+ set << parse_children(element)
32
+ end
33
+ { @name.to_sym => set }
34
+ else
35
+ parse_children(data[@name])
36
+ end
37
+ end
38
+
39
+ def to_hash
40
+ result = {}; value = nil
41
+ if @children.any?
42
+ value = {}
43
+ @children.each{|child| value.merge!(child.to_hash) }
44
+ else
45
+ value = @value
46
+ end
47
+
48
+ result.merge!({@name => value})
49
+ end
50
+
51
+ # Spigot::Map::Definition.new(:user){ username :login }
52
+ # Spigot::Map::Definition.new(:user){ username = :login }
53
+ def method_missing(name, *args, &block)
54
+ name = name.to_s.gsub('=','').to_sym
55
+ @children << Spigot::Map::Definition.new(name, *args, &block)
56
+ end
57
+
58
+ private
59
+
60
+ def parse_children(data)
61
+ child_hash = {}
62
+ @children.each do |child|
63
+ child_hash.merge!(child.parse(data))
64
+ end
65
+ child_hash
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,27 @@
1
+ module Spigot
2
+ module Map
3
+ class Option
4
+
5
+ def initialize(&block)
6
+ @conditions = []
7
+ instance_eval(&block) if block_given?
8
+ end
9
+
10
+ def primary_key(key=nil)
11
+ return @primary_key if key.nil?
12
+ @primary_key = key
13
+ end
14
+
15
+ def foreign_key(key=nil)
16
+ return @foreign_key if key.nil?
17
+ @foreign_key = key
18
+ end
19
+
20
+ def conditions(attributes=nil)
21
+ return @attributes if attributes.nil?
22
+ @conditions = attributes
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ module Spigot
2
+ module Map
3
+ class Resource
4
+
5
+ attr_reader :definitions
6
+
7
+ def initialize(name, &block)
8
+ @name = name.to_s.underscore.to_sym
9
+ @definitions = []
10
+ @options = Spigot::Map::Option.new
11
+ self.instance_eval(&block) if block_given?
12
+ end
13
+
14
+ def append(definition)
15
+ @definitions << definition
16
+ end
17
+
18
+ def options(&block)
19
+ @options = Spigot::Map::Option.new(&block)
20
+ end
21
+
22
+ def to_hash
23
+ resource = {}
24
+ @definitions.each{|rule| resource.merge!(rule.to_hash) }
25
+ {@name => resource}
26
+ end
27
+
28
+ # Spigot::Map::Resource.new(:user){ username :login }
29
+ # Spigot::Map::Resource.new(:user){ username = :login }
30
+ def method_missing(name, *args, &block)
31
+ name = name.to_s.gsub('=','').to_sym
32
+ Spigot::Map::Definition.define(self, name, *args, &block)
33
+ end
34
+
35
+ end
36
+ end
37
+ end