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
@@ -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