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
@@ -1,16 +1,15 @@
1
1
  module Spigot
2
2
  module Map
3
3
  class Definition
4
-
5
- def initialize(name, args=nil, parent=nil, &block)
4
+ def initialize(name, args = nil, parent = nil, &block)
6
5
  @name = name
7
6
  @value = args
8
7
  @children = []
9
- self.instance_eval(&block) if block_given?
8
+ instance_eval(&block) if block_given?
10
9
  @parse = block unless @children.any?
11
10
  end
12
11
 
13
- def self.define(resource, name, value=nil, &block)
12
+ def self.define(resource, name, value = nil, &block)
14
13
  definition = new(name, value, &block)
15
14
  resource.append definition
16
15
  definition
@@ -19,9 +18,19 @@ module Spigot
19
18
  def parse(data)
20
19
  return {} if data.nil?
21
20
 
22
- data.default_proc = proc{|h, k| h.key?(k.to_s) ? h[k.to_s] : nil} if data.is_a?(Hash)
21
+ data.default_proc = proc { |h, k| h.key?(k.to_s) ? h[k.to_s] : nil } if data.is_a?(Hash)
23
22
  if @children.empty?
23
+ unless data.is_a?(Hash)
24
+ raise Spigot::InvalidSchemaError, "Cannot extract :#{@name} from #{data.inspect}"
25
+ end
26
+
24
27
  value = @parse ? @parse.call(data[@name]) : data[@name]
28
+ if @value.is_a?(Class)
29
+ translator = @value.spigot.translator
30
+ translator.data = value
31
+ return { @value.name.underscore.to_sym => translator.format }
32
+ end
33
+
25
34
  return { @value.to_sym => value }
26
35
  end
27
36
 
@@ -37,21 +46,22 @@ module Spigot
37
46
  end
38
47
 
39
48
  def to_hash
40
- result = {}; value = nil
49
+ result = {}
50
+ value = nil
41
51
  if @children.any?
42
52
  value = {}
43
- @children.each{|child| value.merge!(child.to_hash) }
53
+ @children.each { |child| value.merge!(child.to_hash) }
44
54
  else
45
55
  value = @value
46
56
  end
47
57
 
48
- result.merge!({@name => value})
58
+ result.merge!(@name => value)
49
59
  end
50
60
 
51
61
  # Spigot::Map::Definition.new(:user){ username :login }
52
62
  # Spigot::Map::Definition.new(:user){ username = :login }
53
63
  def method_missing(name, *args, &block)
54
- name = name.to_s.gsub('=','').to_sym
64
+ name = name.to_s.gsub('=', '').to_sym
55
65
  @children << Spigot::Map::Definition.new(name, *args, &block)
56
66
  end
57
67
 
@@ -1,27 +1,15 @@
1
1
  module Spigot
2
2
  module Map
3
3
  class Option
4
-
5
4
  def initialize(&block)
6
5
  @conditions = []
7
6
  instance_eval(&block) if block_given?
8
7
  end
9
8
 
10
- def primary_key(key=nil)
9
+ def primary_key(key = nil)
11
10
  return @primary_key if key.nil?
12
11
  @primary_key = key
13
12
  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
13
  end
26
14
  end
27
15
  end
@@ -1,37 +1,42 @@
1
1
  module Spigot
2
2
  module Map
3
3
  class Resource
4
-
5
4
  attr_reader :definitions
6
5
 
7
6
  def initialize(name, &block)
8
7
  @name = name.to_s.underscore.to_sym
9
8
  @definitions = []
10
9
  @options = Spigot::Map::Option.new
11
- self.instance_eval(&block) if block_given?
10
+ instance_eval(&block) if block_given?
12
11
  end
13
12
 
14
13
  def append(definition)
15
14
  @definitions << definition
16
15
  end
17
16
 
17
+ def associations
18
+ definitions.map do |definition|
19
+ value = definition.instance_variable_get(:@value)
20
+ definition if value.is_a?(Class)
21
+ end.compact
22
+ end
23
+
18
24
  def options(&block)
19
25
  @options = Spigot::Map::Option.new(&block)
20
26
  end
21
27
 
22
28
  def to_hash
23
29
  resource = {}
24
- @definitions.each{|rule| resource.merge!(rule.to_hash) }
25
- {@name => resource}
30
+ @definitions.each { |rule| resource.merge!(rule.to_hash) }
31
+ { @name => resource }
26
32
  end
27
33
 
28
34
  # Spigot::Map::Resource.new(:user){ username :login }
29
35
  # Spigot::Map::Resource.new(:user){ username = :login }
30
36
  def method_missing(name, *args, &block)
31
- name = name.to_s.gsub('=','').to_sym
37
+ name = name.to_s.gsub('=', '').to_sym
32
38
  Spigot::Map::Definition.define(self, name, *args, &block)
33
39
  end
34
-
35
40
  end
36
41
  end
37
42
  end
@@ -1,7 +1,6 @@
1
- module Spigot
1
+ module Spigot
2
2
  module Map
3
3
  class Service
4
-
5
4
  attr_reader :name
6
5
  attr_accessor :resources
7
6
 
@@ -17,7 +16,7 @@
17
16
  end
18
17
 
19
18
  def self.resource(name, &block)
20
- service(:any){ resource(name, &block) }
19
+ service(:any) { resource(name, &block) }
21
20
  end
22
21
 
23
22
  def self.find(name)
@@ -33,11 +32,13 @@
33
32
  end
34
33
 
35
34
  def [](name)
36
- resources.detect{|r| r.instance_variable_get(:@name).to_sym == name.to_sym}
35
+ resources.find { |r| r.instance_variable_get(:@name).to_sym == name.to_sym }
37
36
  end
38
37
 
39
38
  def self.extract(params)
40
39
  return params if current_map.nil?
40
+ return [nil, params] if params.is_a?(Array)
41
+
41
42
  name = params.keys.first
42
43
  service = current_map.service(name)
43
44
 
@@ -53,7 +54,6 @@
53
54
  def self.current_map
54
55
  Spigot.config.map
55
56
  end
56
-
57
57
  end
58
58
  end
59
59
  end
@@ -2,10 +2,10 @@ class String
2
2
  # Don't really like patching string here,
3
3
  # but it's fine for now.
4
4
  def underscore
5
- self.gsub(/::/, '/').
6
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
7
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
8
- tr("-", "_").
9
- downcase
5
+ gsub(/::/, '/')
6
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
7
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
8
+ .tr('-', '_')
9
+ .downcase
10
10
  end
11
11
  end
@@ -1,10 +1,15 @@
1
1
  module Spigot
2
2
  class Proxy
3
-
4
3
  ## Proxy
5
4
  #
6
5
  # Spigot::Proxy provides accessor methods used by the implementation
7
6
  # that could be useful for development or custom behavior
7
+ #
8
+ # Usage:
9
+ #
10
+ # User.spigot.find_or_create(data)
11
+ #
12
+ # User.spigot(:twitter).find_or_create(data)
8
13
 
9
14
  attr_reader :resource, :service
10
15
 
@@ -13,45 +18,45 @@ module Spigot
13
18
  #
14
19
  # @param service [String] This is the service that dictates the proxy.
15
20
  # @param resource [Object] This is the class implementing the proxy.
16
- def initialize(resource, service=nil)
21
+ def initialize(resource, service = nil)
17
22
  @service = service
18
23
  @resource = resource
19
24
  end
20
25
 
21
26
  ## #find
22
27
  # Alias for find_by_api
23
- def find(params={})
24
- resource.find_by_api(service_scoped(params))
28
+ def find(params = {})
29
+ resource.find_by_api service_scoped(params)
25
30
  end
26
31
 
27
32
  ## #find_all
28
33
  # Alias for find_all_by_api
29
- def find_all(params={})
30
- resource.find_all_by_api(service_scoped(params))
34
+ def find_all(params = {})
35
+ resource.find_all_by_api service_scoped(params)
31
36
  end
32
37
 
33
38
  ## #create
34
39
  # Alias for create_by_api
35
- def create(params={})
36
- resource.create_by_api(service_scoped(params))
40
+ def create(params = {})
41
+ resource.create_by_api service_scoped(params)
37
42
  end
38
43
 
39
44
  ## #update
40
45
  # Alias for update_by_api
41
- def update(params={})
42
- resource.update_by_api(service_scoped(params))
46
+ def update(params = {})
47
+ resource.update_by_api service_scoped(params)
43
48
  end
44
49
 
45
50
  ## #find_or_create
46
51
  # Alias for find_or_create_by_api
47
- def find_or_create(params={})
48
- resource.find_or_create_by_api(service_scoped(params))
52
+ def find_or_create(params = {})
53
+ resource.find_or_create_by_api service_scoped(params)
49
54
  end
50
55
 
51
56
  ## #create_or_update
52
57
  # Alias for create_or_update_by_api
53
- def create_or_update(params={})
54
- resource.create_or_update_by_api(service_scoped(params))
58
+ def create_or_update(params = {})
59
+ resource.create_or_update_by_api service_scoped(params)
55
60
  end
56
61
 
57
62
  ## #translator
@@ -66,6 +71,12 @@ module Spigot
66
71
  translator.resource_map
67
72
  end
68
73
 
74
+ ## #present?
75
+ # Returns a boolean if the current spigot map has a mapping for the current resource
76
+ def present?
77
+ translator.resource_map?(resource)
78
+ end
79
+
69
80
  ## #options
70
81
  # Return a hash of any service specific options for this translator. `Spigot.config` not included
71
82
  def options
@@ -74,17 +85,18 @@ module Spigot
74
85
 
75
86
  private
76
87
 
77
- def service_scoped(params={})
88
+ def service_scoped(params = {})
78
89
  return params if @service.nil?
79
- return {@service => params} if Spigot.config.map.nil?
90
+ return { @service => params } if Spigot.config.map.nil?
80
91
 
81
92
  key, data = Spigot::Map::Service.extract(params)
82
- return {@service => params} if key.nil?
93
+ return { @service => params } if key.nil?
83
94
 
84
- raise Spigot::InvalidServiceError, "You cannot specify two services" if key.to_sym != @service.to_sym
95
+ if key.to_sym != @service.to_sym
96
+ raise Spigot::InvalidServiceError, 'You cannot specify two services'
97
+ end
85
98
 
86
- {key => data}
99
+ { key => data }
87
100
  end
88
-
89
101
  end
90
102
  end
@@ -1,12 +1,11 @@
1
1
  module Spigot
2
2
  class Record
3
-
4
3
  ## Record
5
4
  #
6
5
  # Spigot::Record is responsible for the instantiation and creation
7
6
  # of objects with the formatted data received from Spigot::Translator.
8
7
 
9
- attr_reader :resource, :record, :data
8
+ attr_reader :service, :resource, :record, :data, :map
10
9
 
11
10
  # #initialize(resource, data)
12
11
  # Method to initialize a record.
@@ -14,10 +13,15 @@ module Spigot
14
13
  # @param resource [Object] This is the class implementing the record.
15
14
  # @param data [Hash] The already formatted data used to produce the object.
16
15
  # @param record [Object] Optional record of `resource` type already in database.
17
- def initialize(resource, data, record=nil)
18
- @resource = resource
19
- @data = data
20
- @record = record
16
+ def initialize(service, resource, data, record = nil)
17
+ @resource = resource
18
+ @data = data
19
+ @record = record
20
+ @service = service
21
+
22
+ proxy = resource.spigot(service)
23
+ @map = proxy.map if proxy.present?
24
+ @associations = map ? map.associations : []
21
25
  end
22
26
 
23
27
  ## #instantiate(resource, data)
@@ -25,8 +29,8 @@ module Spigot
25
29
  #
26
30
  # @param resource [Object] This is the class implementing the record.
27
31
  # @param data [Hash] The already formatted data used to produce the object.
28
- def self.instantiate(resource, data)
29
- new(resource, data).instantiate
32
+ def self.instantiate(service, resource, data)
33
+ new(service, resource, data).instantiate
30
34
  end
31
35
 
32
36
  ## #create(resource, data)
@@ -34,8 +38,8 @@ module Spigot
34
38
  #
35
39
  # @param resource [Object] This is the class implementing the record.
36
40
  # @param data [Hash] The already formatted data used to produce the object.
37
- def self.create(resource, data)
38
- new(resource, data).create
41
+ def self.create(service, resource, data)
42
+ new(service, resource, data).create
39
43
  end
40
44
 
41
45
  ## #update(resource, data)
@@ -44,8 +48,8 @@ module Spigot
44
48
  # @param resource [Object] This is the class implementing the record.
45
49
  # @param record [Object] Optional record of `resource` type already in database.
46
50
  # @param data [Hash] The already formatted data used to produce the object.
47
- def self.update(resource, record, data)
48
- new(resource, data, record).update
51
+ def self.update(service, resource, record, data)
52
+ new(service, resource, data, record).update
49
53
  end
50
54
 
51
55
  ## #instantiate
@@ -57,7 +61,7 @@ module Spigot
57
61
  ## #create
58
62
  # Executes the create method on the implementing resource with formatted data.
59
63
  def create
60
- resource.create(data)
64
+ data.is_a?(Array) ? create_by_array : create_by_hash(data)
61
65
  end
62
66
 
63
67
  ## #update
@@ -67,5 +71,28 @@ module Spigot
67
71
  record.save! if record.changed?
68
72
  end
69
73
 
74
+ private
75
+
76
+ def create_by_array
77
+ data.map { |record| create_by_hash(record) }
78
+ end
79
+
80
+ def create_by_hash(record)
81
+ resolve_associations(record) if @associations.any?
82
+ resource.create(record)
83
+ end
84
+
85
+ def resolve_associations(record)
86
+ @associations.each do |association|
87
+ submodel = association.instance_variable_get(:@value)
88
+
89
+ key = submodel.name.underscore.to_sym
90
+ submodel_data = record.delete(key)
91
+ if submodel_data
92
+ object = Record.create(service, submodel, submodel_data)
93
+ record.merge!("#{key}_id".to_sym => object.id)
94
+ end
95
+ end
96
+ end
70
97
  end
71
98
  end
@@ -3,13 +3,10 @@ require 'hashie'
3
3
 
4
4
  module Spigot
5
5
  class Translator
6
-
7
6
  ## Translator
8
7
  #
9
- # Translator reads the yaml file in the spigot config directory for
10
- # a given service. It looks up the key for the resource class name
11
- # passed in, then translates the data received into the format described
12
- # in the yaml file for that resource.
8
+ # Translator looks up the key for the resource class name of the calling class,
9
+ # then translates the data received into the format described by the Spigot definition
13
10
  #
14
11
  # Relevant Configuration:
15
12
  # config.options_key => The key which the Translator uses to configure a resource_map.
@@ -17,15 +14,15 @@ module Spigot
17
14
  attr_reader :service, :resource
18
15
  attr_accessor :data
19
16
 
20
- OPTIONS = %w(primary_key foreign_key conditions).freeze
17
+ OPTIONS = %w(primary_key).freeze
21
18
 
22
- ## #initialize(service, resource, data)
19
+ ## #initialize(resource, service=nil, data={})
23
20
  # Method to initialize a translator.
24
21
  #
25
- # @param service [Symbol] Service doing the translating. Must have a corresponding yaml file.
26
- # @param resource [Object] This is the class using the translator.
22
+ # @param resource [Object] This is the class implementing the translator.
23
+ # @param service [Symbol] Translation map specific to incoming data.
27
24
  # @param data [Hash] Data in the format received by the api (optional).
28
- def initialize(resource, service=nil, data={})
25
+ def initialize(resource, service = nil, data = {})
29
26
  @service = service
30
27
  @resource = resource.is_a?(Class) ? resource : resource.class
31
28
  raise InvalidResourceError, 'You must provide a calling resource' if resource.nil?
@@ -33,53 +30,37 @@ module Spigot
33
30
  end
34
31
 
35
32
  ## #format
36
- # Formats the hash of data passed in to the format specified in the yaml file.
33
+ # Formats the hash of data passed in to the format specified in the Spigot defintion.
37
34
  def format
38
- @format ||= data.is_a?(Array) ? data.map{|el| parse(el) } : parse(data)
35
+ @format ||= data.is_a?(Array) ? data.map { |el| parse(el) } : parse(data)
39
36
  end
40
37
 
41
- ## #id
42
- # The value at the foreign_key attribute specified in the resource options, defaults to 'id'.
43
- def id
44
- @id ||= lookup(foreign_key)
45
- end
38
+ ## #conditions
39
+ # The conditions used when querying the database for an existing record
40
+ def conditions
41
+ values = []
42
+ if format.is_a?(Array)
43
+ values = format.map { |item| item[primary_key] }
44
+ else
45
+ values = format[primary_key]
46
+ end
46
47
 
47
- ## #lookup(attribute)
48
- # Find the value in the unformatted api data that matches the passed in key.
49
- #
50
- # @param attribute [String] The key pointing to the value you wish to lookup.
51
- def lookup(attribute)
52
- data.detect{|k, v| k.to_s == attribute.to_s }.try(:last)
48
+ { primary_key => values }
53
49
  end
54
50
 
55
51
  ## #options
56
52
  # Available options per resource.
57
- #
58
- # @primary_key:
59
- # Default: "#{service}_id"
60
- # Name of the column in your local database that serves as id for an external resource.
61
- # @foreign_key:
62
- # Default: "id"
63
- # Name of the key representing the resource's ID in the data received from the API.
64
- # @conditions:
65
- # Default: nil
66
- # Array of attributes included in the database query, these are names of columns in your database.
67
53
  def options
68
54
  @options ||= resource_map.instance_variable_get(:@options)
69
55
  end
70
56
 
57
+ # @primary_key:
58
+ # Default: "#{service}_id"
59
+ # Name of the column in your local database that serves as id for an external resource.
71
60
  def primary_key
72
61
  options.primary_key || "#{service}_id"
73
62
  end
74
63
 
75
- def foreign_key
76
- options.foreign_key || 'id'
77
- end
78
-
79
- def conditions
80
- {primary_key => format[primary_key]}
81
- end
82
-
83
64
  ## #resource_map
84
65
  # Return the mapped resource object for the current service and resource
85
66
  def resource_map
@@ -90,6 +71,10 @@ module Spigot
90
71
  @resource_map
91
72
  end
92
73
 
74
+ def resource_map?(key)
75
+ service_map[key.to_s.underscore]
76
+ end
77
+
93
78
  private
94
79
 
95
80
  def parse(dataset)
@@ -109,12 +94,11 @@ module Spigot
109
94
  if service.nil?
110
95
  raise MissingResourceError, "There is no #{resource.to_s.underscore} resource_map"
111
96
  else
112
- raise InvalidServiceError, "No definition found for #{service}"
97
+ raise InvalidServiceError, "No #{resource.to_s} definition found for #{service}"
113
98
  end
114
99
  end
115
100
 
116
101
  @service_map
117
102
  end
118
-
119
103
  end
120
104
  end