mad_cart 0.0.1
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.
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +117 -0
- data/Rakefile +9 -0
- data/lib/mad_cart/attribute_mapper.rb +35 -0
- data/lib/mad_cart/configuration.rb +59 -0
- data/lib/mad_cart/inheritable_attributes.rb +27 -0
- data/lib/mad_cart/model/base.rb +68 -0
- data/lib/mad_cart/model/customer.rb +9 -0
- data/lib/mad_cart/model/product.rb +9 -0
- data/lib/mad_cart/store/base.rb +151 -0
- data/lib/mad_cart/store/big_commerce.rb +118 -0
- data/lib/mad_cart/store/etsy.rb +56 -0
- data/lib/mad_cart/version.rb +3 -0
- data/lib/mad_cart.rb +23 -0
- data/mad_cart.gemspec +33 -0
- data/spec/fixtures/vcr_cassettes/big_commerce.yml +17830 -0
- data/spec/fixtures/vcr_cassettes/big_commerce_no_records.yml +171 -0
- data/spec/fixtures/vcr_cassettes/etsy_store_listings.yml +600 -0
- data/spec/lib/configuration_spec.rb +52 -0
- data/spec/lib/mad_cart_spec.rb +25 -0
- data/spec/lib/model/base_spec.rb +49 -0
- data/spec/lib/model/customer_spec.rb +58 -0
- data/spec/lib/model/product_spec.rb +74 -0
- data/spec/lib/store/base_spec.rb +231 -0
- data/spec/lib/store/big_commerce_spec.rb +117 -0
- data/spec/lib/store/etsy_spec.rb +91 -0
- data/spec/spec_helper.rb +42 -0
- metadata +284 -0
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
vendor
|
13
|
+
pkg
|
14
|
+
rdoc
|
15
|
+
spec/reports
|
16
|
+
test/tmp
|
17
|
+
test/version_tmp
|
18
|
+
tmp
|
19
|
+
.ruby-version
|
20
|
+
.ruby-gemset
|
21
|
+
.DS_Store
|
22
|
+
*.swp
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 MadMimi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# MadCart
|
2
|
+
|
3
|
+
Provides a unified API to various CRMs and online stores.
|
4
|
+
Simple configuration allows you to specify the properties you're interested in and what they're called.
|
5
|
+
A flexible DSL allows easy CRM and store integration.
|
6
|
+
|
7
|
+
Currently supports the following stores:
|
8
|
+
**-Etsy**
|
9
|
+
**-Bigcommerce**
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'mad_cart'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install mad_cart
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
#### Credentials
|
28
|
+
|
29
|
+
Store/CRM credentials can be configured:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
MadCart.configure do |config|
|
33
|
+
config.add_store :etsy, api_key: 'my-api-key', store_url: 'http://path.to/store'
|
34
|
+
end
|
35
|
+
|
36
|
+
store = MadCart::Store::Etsy.new
|
37
|
+
```
|
38
|
+
...or passed to the store initializer:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
store = MadCart::Store::Etsy.new(api_key: 'my-api-key', store_url: 'http://path.to/store')
|
42
|
+
```
|
43
|
+
|
44
|
+
### Products
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
store = MadCart::Store::Etsy.new
|
48
|
+
|
49
|
+
store.products
|
50
|
+
#=> an array of MadCart::Model::Product objects
|
51
|
+
```
|
52
|
+
|
53
|
+
### Customers
|
54
|
+
```ruby
|
55
|
+
store = MadCart::Store::BigCommerce.new
|
56
|
+
|
57
|
+
store.customers
|
58
|
+
# => returns an array of MadCart::Model::Customer objects
|
59
|
+
```
|
60
|
+
|
61
|
+
### Attributes
|
62
|
+
|
63
|
+
Each model object has a property called attributes, which returns a hash. By default the following properties are returned:
|
64
|
+
|
65
|
+
**Customers:** *first_name*, *last_name*, and *email*
|
66
|
+
**Products:** *name*, *description*, and *image_url*
|
67
|
+
|
68
|
+
#### Additional Attributes
|
69
|
+
|
70
|
+
Any additional attributes returned by the CRM or store API will be stored in the *additional_attributes* property of the object.
|
71
|
+
MadCart allows you to include any of these additional attributes in the #attributes property of the model objects:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
store = MadCart::Store::Etsy.new
|
75
|
+
|
76
|
+
store.products.first.attributes
|
77
|
+
#=> {name: 'product name', description: 'product description', image_url 'http://path.to/image'}
|
78
|
+
|
79
|
+
MadCart.configure do |config|
|
80
|
+
config.include_attributes products: [:external_id, :url]
|
81
|
+
end
|
82
|
+
|
83
|
+
store.products.first.attributes
|
84
|
+
#=> {name: 'product name', description: 'product description', image_url 'http://path.to/image', external_id: 42, url: 'http://path.to/store/products/42'}
|
85
|
+
```
|
86
|
+
|
87
|
+
#### Attribute Names
|
88
|
+
|
89
|
+
MadCart allows you to change the names of these attributes to match your existing field names:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
MadCart.configure do |config|
|
93
|
+
config.attribute_map :products, {"name" => "title"}
|
94
|
+
end
|
95
|
+
|
96
|
+
store = MadCart::Store::Etsy.new
|
97
|
+
|
98
|
+
store.products.first.attributes
|
99
|
+
#=> {title: 'product name', description: 'product description', image_url 'http://path.to/image'}
|
100
|
+
```
|
101
|
+
|
102
|
+
This, in combination with declaring additional attributes, allows for very thin integration points without sacrificing customizability:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
store = MadCart::Store::Etsy.new
|
106
|
+
store.products.each{|p| MyProduct.create!(p.attributes) }
|
107
|
+
```
|
108
|
+
|
109
|
+
## Contributing
|
110
|
+
|
111
|
+
See the [Contributor's Guide](https://github.com/madmimi/mad_cart/wiki/Contributor's-Guide) for info on the store integration API.
|
112
|
+
|
113
|
+
1. Fork it
|
114
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
115
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
116
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
117
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module MadCart
|
2
|
+
module AttributeMapper
|
3
|
+
|
4
|
+
# def attributes
|
5
|
+
# Hash[initial_attributes.map {|k, v| [(mapping_hash[k] || mapping_hash[k.to_sym] || k).to_s, v] }]
|
6
|
+
# end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
def attributes
|
13
|
+
Hash[self.class.exposed_attributes.map{|a| [a.to_s, self.send(a)]}]
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def map_attribute_name(name)
|
18
|
+
mapping_hash[name.to_sym] || mapping_hash[name.to_s] || name
|
19
|
+
end
|
20
|
+
|
21
|
+
def mapping_hash
|
22
|
+
MadCart.config.attribute_maps[self.to_s.demodulize.underscore.pluralize] || {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def mapped_attributes
|
26
|
+
mapping_hash.values
|
27
|
+
end
|
28
|
+
|
29
|
+
def unmapped_attributes
|
30
|
+
mapping_hash.keys
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module MadCart
|
4
|
+
class << self
|
5
|
+
def configure
|
6
|
+
raise(ArgumentError, "MadCart.configure requires a block argument.") unless block_given?
|
7
|
+
yield(MadCart::Configuration.instance)
|
8
|
+
end
|
9
|
+
|
10
|
+
def config
|
11
|
+
raise(ArgumentError, "MadCart.config does not support blocks. Use MadCart.configure to set config values.") if block_given?
|
12
|
+
return MadCart::Configuration.instance.data
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Configuration
|
17
|
+
include Singleton
|
18
|
+
|
19
|
+
def add_store(store_name, args={})
|
20
|
+
setup_data
|
21
|
+
|
22
|
+
@data[:stores] << store_name
|
23
|
+
@data[store_name.to_s] = args
|
24
|
+
end
|
25
|
+
|
26
|
+
def attribute_map(data_model, attributes)
|
27
|
+
setup_data
|
28
|
+
|
29
|
+
@data[:attribute_maps][data_model.to_s] = attributes
|
30
|
+
end
|
31
|
+
|
32
|
+
def include_attributes(args={})
|
33
|
+
setup_data
|
34
|
+
|
35
|
+
@data[:included_attributes].merge!(args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def data
|
39
|
+
setup_data
|
40
|
+
Data.new(@data)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def setup_data
|
45
|
+
@data ||= {:stores => []}
|
46
|
+
@data[:attribute_maps] ||= {}
|
47
|
+
@data[:included_attributes] ||= {}
|
48
|
+
end
|
49
|
+
|
50
|
+
class Data < OpenStruct
|
51
|
+
class ConfigurationError < NoMethodError; end
|
52
|
+
|
53
|
+
def method_missing(meth, *args)
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module MadCart
|
2
|
+
module InheritableAttributes
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def inheritable_attributes(*args)
|
9
|
+
@madcart_inheritable_attributes ||= [:inheritable_attributes]
|
10
|
+
@madcart_inheritable_attributes += args
|
11
|
+
args.each do |arg|
|
12
|
+
class_eval %(
|
13
|
+
class << self; attr_accessor :#{arg} end
|
14
|
+
)
|
15
|
+
end
|
16
|
+
@madcart_inheritable_attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
def inherited(subclass)
|
20
|
+
@madcart_inheritable_attributes.each do |inheritable_attribute|
|
21
|
+
instance_var = "@#{inheritable_attribute}"
|
22
|
+
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module MadCart
|
2
|
+
module Model
|
3
|
+
module Base
|
4
|
+
def initialize(args={})
|
5
|
+
self.additional_attributes = {}
|
6
|
+
check_required_attributes(args)
|
7
|
+
args.each { |k,v| set_attribute(k, v) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.extend ClassMethods
|
12
|
+
base.class_eval do
|
13
|
+
include AttributeMapper
|
14
|
+
include InheritableAttributes
|
15
|
+
attr_accessor :additional_attributes
|
16
|
+
inheritable_attributes :required_attrs
|
17
|
+
attr_accessor *exposed_attributes
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def define_attribute_accessors
|
22
|
+
klass.class_eval do
|
23
|
+
attr_accessor(*exposed_attributes)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_required_attributes(args)
|
28
|
+
keys = args.keys.map{|a| a.to_s }
|
29
|
+
klass.required_attrs.each do |attr|
|
30
|
+
raise(ArgumentError, "missing argument: #{attr}") if !keys.include?(attr)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
private :check_required_attributes
|
34
|
+
|
35
|
+
def set_attribute(key, value)
|
36
|
+
attr_name = klass.map_attribute_name(key)
|
37
|
+
|
38
|
+
if klass.exposed_attributes.include? attr_name.to_s
|
39
|
+
define_attribute_accessors unless self.respond_to?(attr_name)
|
40
|
+
self.send("#{attr_name}=", value) unless value.nil?
|
41
|
+
else
|
42
|
+
self.additional_attributes[attr_name.to_s] = value unless value.nil?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
private :set_attribute
|
46
|
+
|
47
|
+
def klass
|
48
|
+
self.class
|
49
|
+
end
|
50
|
+
private :klass
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
def required_attributes(*args)
|
54
|
+
@required_attrs = args.map{|a| a.to_s }
|
55
|
+
attr_accessor *args
|
56
|
+
end
|
57
|
+
|
58
|
+
def exposed_attributes
|
59
|
+
((self.required_attrs || []) + included_attributes + mapped_attributes).uniq.map{|a| a.to_s } - unmapped_attributes.map{|a| a.to_s }
|
60
|
+
end
|
61
|
+
|
62
|
+
def included_attributes
|
63
|
+
MadCart.config.included_attributes[self.to_s.demodulize.underscore.pluralize.to_sym] || []
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module MadCart
|
2
|
+
module Store
|
3
|
+
class SetupError < StandardError
|
4
|
+
def self.message
|
5
|
+
"It appears MyStore has overrided the default "\
|
6
|
+
"MadCart::Base initialize method. That's fine, but please store "\
|
7
|
+
"any required connection arguments as @init_args for the "\
|
8
|
+
"#connection method to use later. Remember to call #after_initialize "\
|
9
|
+
"in your initialize method should you require it."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
class InvalidStore < StandardError; end
|
13
|
+
|
14
|
+
module Base
|
15
|
+
def self.included(base)
|
16
|
+
base.extend ClassMethods
|
17
|
+
base.class_eval do
|
18
|
+
include InheritableAttributes
|
19
|
+
inheritable_attributes :connection_delegate, :required_connection_args,
|
20
|
+
:fetch_delegates, :format_delegates, :after_init_delegate
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(*args)
|
25
|
+
set_init_args(*args)
|
26
|
+
after_initialize(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def connection
|
30
|
+
validate_connection_args!
|
31
|
+
return init_connection
|
32
|
+
end
|
33
|
+
|
34
|
+
def init_connection
|
35
|
+
@connection ||= execute_delegate(klass.connection_delegate, @init_args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def klass
|
39
|
+
self.class
|
40
|
+
end
|
41
|
+
private :klass
|
42
|
+
|
43
|
+
def execute_delegate(delegate, *args)
|
44
|
+
return self.send(delegate, *args) if delegate.is_a?(Symbol)
|
45
|
+
return delegate.call(*args) if delegate.is_a?(Proc)
|
46
|
+
|
47
|
+
raise ArgumentError, "Invalid delegate" # if not returned by now
|
48
|
+
end
|
49
|
+
private :execute_delegate
|
50
|
+
|
51
|
+
def after_initialize(*args)
|
52
|
+
return nil unless klass.after_init_delegate
|
53
|
+
execute_delegate(klass.after_init_delegate, *args)
|
54
|
+
end
|
55
|
+
private :after_initialize
|
56
|
+
|
57
|
+
def validate_connection_args!
|
58
|
+
return true if klass.required_connection_args.empty?
|
59
|
+
|
60
|
+
raise(SetupError, SetupError.message) if @init_args.nil?
|
61
|
+
raise(ArgumentError,"Missing connection arguments: "\
|
62
|
+
"#{missing_args.join(', ')}") if missing_args.present?
|
63
|
+
end
|
64
|
+
private :validate_connection_args!
|
65
|
+
|
66
|
+
def missing_args
|
67
|
+
klass.required_connection_args - @init_args.keys
|
68
|
+
end
|
69
|
+
private :missing_args
|
70
|
+
|
71
|
+
def set_init_args(*args)
|
72
|
+
@init_args ||= configured_connection_args.merge(args.last || {})
|
73
|
+
end
|
74
|
+
private :set_init_args
|
75
|
+
|
76
|
+
def configured_connection_args
|
77
|
+
MadCart.config.send(klass.to_s.demodulize.underscore) || {}
|
78
|
+
end
|
79
|
+
private :configured_connection_args
|
80
|
+
|
81
|
+
def ensure_model_format(model, results)
|
82
|
+
if results.first.is_a?(MadCart::Model::Base)
|
83
|
+
results
|
84
|
+
else
|
85
|
+
map_to_madcart_model(model, results)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
private :ensure_model_format
|
89
|
+
|
90
|
+
def map_to_madcart_model(model, results)
|
91
|
+
results.map do |args|
|
92
|
+
"MadCart::Model::#{model.to_s.classify}".constantize.new(args)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
private :map_to_madcart_model
|
96
|
+
|
97
|
+
module ClassMethods
|
98
|
+
def create_connection_with(*args)
|
99
|
+
@connection_delegate = parse_delegate(args.first, :create_connection_with)
|
100
|
+
opts = args[1] || {}
|
101
|
+
@required_connection_args = opts[:requires] || []
|
102
|
+
end
|
103
|
+
|
104
|
+
def fetch(model, options={})
|
105
|
+
@fetch_delegates ||= {}
|
106
|
+
@format_delegates ||= {}
|
107
|
+
@fetch_delegates[model] = parse_delegate(options, :fetch)
|
108
|
+
|
109
|
+
define_method_for(model)
|
110
|
+
end
|
111
|
+
|
112
|
+
def format(model, options={})
|
113
|
+
if @fetch_delegates[model].nil?
|
114
|
+
raise ArgumentError, "Cannot define 'format' for a model that has not defined 'fetch'"
|
115
|
+
end
|
116
|
+
|
117
|
+
@format_delegates[model] = parse_delegate(options, :format)
|
118
|
+
end
|
119
|
+
|
120
|
+
def after_initialize(*args)
|
121
|
+
@after_init_delegate = parse_delegate(args.first, :after_initialize)
|
122
|
+
end
|
123
|
+
|
124
|
+
def parse_delegate(arg, method)
|
125
|
+
return arg if (arg.is_a?(Symbol) || arg.is_a?(Proc))
|
126
|
+
return arg[:with] if arg.is_a?(Hash) && arg[:with]
|
127
|
+
|
128
|
+
raise ArgumentError, "Invalid delegate for #{method}: "\
|
129
|
+
"#{arg.first.class}. Use Proc or Symbol. "
|
130
|
+
end
|
131
|
+
private :parse_delegate
|
132
|
+
|
133
|
+
def define_method_for(model)
|
134
|
+
define_method model do |*args|
|
135
|
+
fetch_result = execute_delegate(self.class.fetch_delegates[model], *args)
|
136
|
+
formatted_result = if self.class.format_delegates[model]
|
137
|
+
formatter = self.class.format_delegates[model]
|
138
|
+
fetch_result.map{|r| execute_delegate(formatter, r)}
|
139
|
+
else
|
140
|
+
fetch_result
|
141
|
+
end
|
142
|
+
|
143
|
+
return ensure_model_format(model, formatted_result)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
private :define_method_for
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module MadCart
|
2
|
+
module Store
|
3
|
+
class BigCommerce
|
4
|
+
class InvalidStore < StandardError; end;
|
5
|
+
class ServerError < StandardError; end;
|
6
|
+
class InvalidCredentials < StandardError; end;
|
7
|
+
|
8
|
+
include MadCart::Store::Base
|
9
|
+
|
10
|
+
create_connection_with :create_connection, :requires => [:api_key, :store_url, :username]
|
11
|
+
fetch :customers, :with => :get_customer_hashes
|
12
|
+
fetch :products, :with => :get_products
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
check_for_errors do
|
16
|
+
connection.get('time.json')
|
17
|
+
end
|
18
|
+
return true
|
19
|
+
|
20
|
+
rescue InvalidCredentials => e
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
|
24
|
+
def products_count
|
25
|
+
(parse_response { connection.get('products/count.json') })["count"]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def make_customer_request(params={:min_id => 1})
|
31
|
+
parse_response { connection.get('customers.json', params) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_products(options={})
|
35
|
+
product_hashes = connection.get('products.json', options).try(:body)
|
36
|
+
return [] unless product_hashes
|
37
|
+
|
38
|
+
threads, images = [], []
|
39
|
+
product_hashes.each do |product|
|
40
|
+
threads << Thread.new do
|
41
|
+
if product["images"]
|
42
|
+
url = "#{product["images"]["resource"][1..-1]}.json"
|
43
|
+
images << parse_response { connection.get(url) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
threads.each { |t| t.join }
|
48
|
+
|
49
|
+
product_hashes.map do |p|
|
50
|
+
|
51
|
+
product_images = images.find { |i| i.first['product_id'] == p['id'] }
|
52
|
+
thumbnail = product_images.find { |i| i["is_thumbnail"] }
|
53
|
+
image = product_images.sort_by{|i| i["sort_order"] }.find { |i| i["is_thumbnail"] }
|
54
|
+
|
55
|
+
p.merge({
|
56
|
+
:image_square_url => connection.build_url("/product_images/#{thumbnail['image_file']}").to_s,
|
57
|
+
:image_url => connection.build_url("/product_images/#{image['image_file']}").to_s,
|
58
|
+
})
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_customer_hashes
|
63
|
+
result = []
|
64
|
+
loop(:make_customer_request) {|c| result << c }
|
65
|
+
return result
|
66
|
+
end
|
67
|
+
|
68
|
+
def loop(source, &block)
|
69
|
+
|
70
|
+
items = send(source, :min_id => 1)
|
71
|
+
|
72
|
+
while true
|
73
|
+
items.each &block
|
74
|
+
break if items.count < 200
|
75
|
+
items = send(source, :min_id => items.max_id + 1 )
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_response(&block)
|
81
|
+
response = check_for_errors &block
|
82
|
+
return [] if empty_body?(response)
|
83
|
+
return response.body
|
84
|
+
end
|
85
|
+
|
86
|
+
def check_for_errors(&block)
|
87
|
+
response = yield
|
88
|
+
|
89
|
+
case response.status
|
90
|
+
when 401
|
91
|
+
raise InvalidCredentials
|
92
|
+
when 500
|
93
|
+
raise ServerError
|
94
|
+
end
|
95
|
+
|
96
|
+
response
|
97
|
+
rescue Faraday::Error::ConnectionFailed => e
|
98
|
+
raise InvalidStore
|
99
|
+
end
|
100
|
+
|
101
|
+
def api_url_for(store_domain)
|
102
|
+
"https://#{store_domain}/api/v2/"
|
103
|
+
end
|
104
|
+
|
105
|
+
def empty_body?(response)
|
106
|
+
true if response.status == 204 || response.body.nil?
|
107
|
+
end
|
108
|
+
|
109
|
+
def create_connection(args={})
|
110
|
+
@connection = Faraday.new(:url => api_url_for(args[:store_url]))
|
111
|
+
@connection.basic_auth(args[:username], args[:api_key])
|
112
|
+
@connection.response :json
|
113
|
+
@connection.adapter Faraday.default_adapter
|
114
|
+
@connection
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|