shopify_api 1.2.5 → 2.0.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 (58) hide show
  1. data/CHANGELOG +10 -0
  2. data/README.rdoc +6 -1
  3. data/RELEASING +16 -0
  4. data/lib/active_resource/connection_ext.rb +16 -0
  5. data/lib/shopify_api.rb +12 -533
  6. data/lib/shopify_api/cli.rb +9 -9
  7. data/lib/shopify_api/countable.rb +7 -0
  8. data/lib/shopify_api/events.rb +7 -0
  9. data/lib/shopify_api/json_format.rb +23 -0
  10. data/lib/shopify_api/limits.rb +76 -0
  11. data/lib/shopify_api/metafields.rb +18 -0
  12. data/lib/shopify_api/resources.rb +40 -0
  13. data/lib/shopify_api/resources/address.rb +4 -0
  14. data/lib/shopify_api/resources/application_charge.rb +9 -0
  15. data/lib/shopify_api/resources/article.rb +12 -0
  16. data/lib/shopify_api/resources/asset.rb +95 -0
  17. data/lib/shopify_api/resources/base.rb +6 -0
  18. data/lib/shopify_api/resources/billing_address.rb +4 -0
  19. data/lib/shopify_api/resources/blog.rb +10 -0
  20. data/lib/shopify_api/resources/collect.rb +5 -0
  21. data/lib/shopify_api/resources/comment.rb +13 -0
  22. data/lib/shopify_api/resources/country.rb +4 -0
  23. data/lib/shopify_api/resources/custom_collection.rb +19 -0
  24. data/lib/shopify_api/resources/customer.rb +4 -0
  25. data/lib/shopify_api/resources/customer_group.rb +4 -0
  26. data/lib/shopify_api/resources/event.rb +10 -0
  27. data/lib/shopify_api/resources/fulfillment.rb +5 -0
  28. data/lib/shopify_api/resources/image.rb +16 -0
  29. data/lib/shopify_api/resources/line_item.rb +4 -0
  30. data/lib/shopify_api/resources/metafield.rb +15 -0
  31. data/lib/shopify_api/resources/note_attribute.rb +4 -0
  32. data/lib/shopify_api/resources/option.rb +4 -0
  33. data/lib/shopify_api/resources/order.rb +25 -0
  34. data/lib/shopify_api/resources/page.rb +6 -0
  35. data/lib/shopify_api/resources/payment_details.rb +4 -0
  36. data/lib/shopify_api/resources/product.rb +33 -0
  37. data/lib/shopify_api/resources/product_search_engine.rb +4 -0
  38. data/lib/shopify_api/resources/province.rb +5 -0
  39. data/lib/shopify_api/resources/receipt.rb +4 -0
  40. data/lib/shopify_api/resources/recurring_application_charge.rb +23 -0
  41. data/lib/shopify_api/resources/redirect.rb +4 -0
  42. data/lib/shopify_api/resources/rule.rb +4 -0
  43. data/lib/shopify_api/resources/script_tag.rb +4 -0
  44. data/lib/shopify_api/resources/shipping_address.rb +4 -0
  45. data/lib/shopify_api/resources/shipping_line.rb +4 -0
  46. data/lib/shopify_api/resources/shop.rb +23 -0
  47. data/lib/shopify_api/resources/smart_collection.rb +10 -0
  48. data/lib/shopify_api/resources/tax_line.rb +4 -0
  49. data/lib/shopify_api/resources/theme.rb +4 -0
  50. data/lib/shopify_api/resources/transaction.rb +5 -0
  51. data/lib/shopify_api/resources/variant.rb +11 -0
  52. data/lib/shopify_api/resources/webhook.rb +4 -0
  53. data/lib/shopify_api/session.rb +165 -0
  54. data/shopify_api.gemspec +13 -92
  55. data/test/cli_test.rb +109 -0
  56. data/test/limits_test.rb +37 -0
  57. data/test/shopify_api_test.rb +13 -1
  58. metadata +76 -82
@@ -8,10 +8,6 @@ module ShopifyAPI
8
8
  class ConfigFileError < StandardError
9
9
  end
10
10
 
11
- tasks.keys.abbrev.each do |shortcut, command|
12
- map shortcut => command.to_sym
13
- end
14
-
15
11
  desc "list", "list available connections"
16
12
  def list
17
13
  available_connections.each do |c|
@@ -55,7 +51,7 @@ module ShopifyAPI
55
51
  file = config_file(connection)
56
52
  if File.exist?(file)
57
53
  if ENV['EDITOR'].present?
58
- `#{ENV['EDITOR']} #{file}`
54
+ system(ENV['EDITOR'], file)
59
55
  else
60
56
  puts "Please set an editor in the EDITOR environment variable"
61
57
  end
@@ -84,7 +80,7 @@ module ShopifyAPI
84
80
  remove_file(default_symlink)
85
81
  `ln -s #{target} #{default_symlink}`
86
82
  else
87
- no_config_file_error(file)
83
+ no_config_file_error(target)
88
84
  end
89
85
  end
90
86
  if File.exist?(default_symlink)
@@ -107,7 +103,11 @@ module ShopifyAPI
107
103
  ARGV.clear
108
104
  IRB.start
109
105
  end
110
-
106
+
107
+ tasks.keys.abbrev.each do |shortcut, command|
108
+ map shortcut => command.to_sym
109
+ end
110
+
111
111
  private
112
112
 
113
113
  def shop_config_dir
@@ -155,7 +155,7 @@ module ShopifyAPI
155
155
  end
156
156
 
157
157
  def no_config_file_error(filename)
158
- raise ConfigFileError, "There is no config file at #{file}"
158
+ raise ConfigFileError, "There is no config file at #{filename}"
159
159
  end
160
160
  end
161
- end
161
+ end
@@ -0,0 +1,7 @@
1
+ module ShopifyAPI
2
+ module Countable
3
+ def count(options = {})
4
+ Integer(get(:count, options))
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module ShopifyAPI
2
+ module Events
3
+ def events
4
+ Event.find(:all, :params => {:resource => self.class.collection_name, :resource_id => id})
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ module ActiveResource
2
+ class Base
3
+ def encode(options = {})
4
+ same = dup
5
+ same.attributes = {self.class.element_name => same.attributes} if self.class.format.extension == 'json'
6
+
7
+ same.send("to_#{self.class.format.extension}", options)
8
+ end
9
+ end
10
+
11
+ module Formats
12
+ module JsonFormat
13
+ def decode(json)
14
+ data = ActiveSupport::JSON.decode(json)
15
+ if data.is_a?(Hash) && data.keys.size == 1
16
+ data.values.first
17
+ else
18
+ data
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,76 @@
1
+ module ShopifyAPI
2
+ module Limits
3
+ def self.included(klass)
4
+ klass.send(:extend, ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ # Takes form num_requests_executed/max_requests
10
+ # Eg: 101/3000
11
+ CREDIT_LIMIT_HEADER_PARAM = {
12
+ :global => 'http_x_shopify_api_call_limit',
13
+ :shop => 'http_x_shopify_shop_api_call_limit'
14
+ }
15
+
16
+ ##
17
+ # How many more API calls can I make?
18
+ # @return {Integer}
19
+ #
20
+ def credit_left
21
+ shop = credit_limit(:shop) - credit_used(:shop)
22
+ global = credit_limit(:global) - credit_used(:global)
23
+ shop < global ? shop : global
24
+ end
25
+ alias_method :available_calls, :credit_left
26
+
27
+ ##
28
+ # Have I reached my API call limit?
29
+ # @return {Boolean}
30
+ #
31
+ def credit_maxed?
32
+ credit_left <= 0
33
+ end
34
+ alias_method :maxed?, :credit_maxed?
35
+
36
+ ##
37
+ # How many total API calls can I make?
38
+ # NOTE: subtracting 1 from credit_limit because I think ShopifyAPI cuts off at 299/2999 or shop/global limits.
39
+ # @param {Symbol} scope [:shop|:global]
40
+ # @return {Integer}
41
+ #
42
+ def credit_limit(scope=:shop)
43
+ @api_credit_limit ||= {}
44
+ @api_credit_limit[scope] ||= api_credit_limit_param(scope).pop.to_i - 1
45
+ end
46
+ alias_method :call_limit, :credit_limit
47
+
48
+ ##
49
+ # How many API calls have I made?
50
+ # @param {Symbol} scope [:shop|:global]
51
+ # @return {Integer}
52
+ #
53
+ def credit_used(scope=:shop)
54
+ api_credit_limit_param(scope).shift.to_i
55
+ end
56
+ alias_method :call_count, :credit_used
57
+
58
+ ##
59
+ # @return {HTTPResonse}
60
+ #
61
+ def response
62
+ Shop.current unless ShopifyAPI::Base.connection.response
63
+ ShopifyAPI::Base.connection.response
64
+ end
65
+
66
+ private
67
+
68
+ ##
69
+ # @return {Array}
70
+ #
71
+ def api_credit_limit_param(scope)
72
+ response[CREDIT_LIMIT_HEADER_PARAM[scope]].split('/')
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,18 @@
1
+ module ShopifyAPI
2
+ module Metafields
3
+ def metafields
4
+ Metafield.find(:all, :params => {:resource => self.class.collection_name, :resource_id => id})
5
+ end
6
+
7
+ def add_metafield(metafield)
8
+ raise ArgumentError, "You can only add metafields to resource that has been saved" if new?
9
+
10
+ metafield.prefix_options = {
11
+ :resource => self.class.collection_name,
12
+ :resource_id => id
13
+ }
14
+ metafield.save
15
+ metafield
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,40 @@
1
+ require 'shopify_api/resources/base'
2
+ require 'shopify_api/resources/address'
3
+ require 'shopify_api/resources/application_charge'
4
+ require 'shopify_api/resources/article'
5
+ require 'shopify_api/resources/asset'
6
+ require 'shopify_api/resources/billing_address'
7
+ require 'shopify_api/resources/blog'
8
+ require 'shopify_api/resources/collect'
9
+ require 'shopify_api/resources/comment'
10
+ require 'shopify_api/resources/country'
11
+ require 'shopify_api/resources/custom_collection'
12
+ require 'shopify_api/resources/customer_group'
13
+ require 'shopify_api/resources/customer'
14
+ require 'shopify_api/resources/event'
15
+ require 'shopify_api/resources/fulfillment'
16
+ require 'shopify_api/resources/image'
17
+ require 'shopify_api/resources/line_item'
18
+ require 'shopify_api/resources/metafield'
19
+ require 'shopify_api/resources/note_attribute'
20
+ require 'shopify_api/resources/option'
21
+ require 'shopify_api/resources/order'
22
+ require 'shopify_api/resources/page'
23
+ require 'shopify_api/resources/payment_details'
24
+ require 'shopify_api/resources/product'
25
+ require 'shopify_api/resources/product_search_engine'
26
+ require 'shopify_api/resources/province'
27
+ require 'shopify_api/resources/receipt'
28
+ require 'shopify_api/resources/recurring_application_charge'
29
+ require 'shopify_api/resources/redirect'
30
+ require 'shopify_api/resources/rule'
31
+ require 'shopify_api/resources/script_tag'
32
+ require 'shopify_api/resources/shipping_address'
33
+ require 'shopify_api/resources/shipping_line'
34
+ require 'shopify_api/resources/shop'
35
+ require 'shopify_api/resources/smart_collection'
36
+ require 'shopify_api/resources/tax_line'
37
+ require 'shopify_api/resources/theme'
38
+ require 'shopify_api/resources/transaction'
39
+ require 'shopify_api/resources/variant'
40
+ require 'shopify_api/resources/webhook'
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class Address < Base
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module ShopifyAPI
2
+ class ApplicationCharge < Base
3
+ undef_method :test
4
+
5
+ def activate
6
+ load_attributes_from_response(post(:activate))
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module ShopifyAPI
2
+ class Article < Base
3
+ include Events
4
+ include Metafields
5
+
6
+ self.prefix = "/admin/blogs/:blog_id/"
7
+
8
+ def comments
9
+ Comment.find(:all, :params => { :article_id => id })
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,95 @@
1
+ module ShopifyAPI
2
+ # Assets represent the files that comprise your theme.
3
+ # There are different buckets which hold different kinds
4
+ # of assets, each corresponding to one of the folders
5
+ # within a theme's zip file: "layout", "templates",
6
+ # "snippets", "assets", and "config". The full key of an
7
+ # asset always starts with the bucket name, and the path
8
+ # separator is a forward slash, like layout/theme.liquid
9
+ # or assets/bg-body.gif.
10
+ #
11
+ # Initialize with a key:
12
+ # asset = ShopifyAPI::Asset.new(:key => 'assets/special.css', :theme_id => 12345)
13
+ #
14
+ # Find by key:
15
+ # asset = ShopifyAPI::Asset.find('assets/image.png', :params => {:theme_id => 12345})
16
+ #
17
+ # Get the text or binary value:
18
+ # asset.value # decodes from attachment attribute if necessary
19
+ #
20
+ # You can provide new data for assets in a few different ways:
21
+ #
22
+ # * assign text data for the value directly:
23
+ # asset.value = "div.special {color:red;}"
24
+ #
25
+ # * provide binary data for the value:
26
+ # asset.attach(File.read('image.png'))
27
+ #
28
+ # * set a URL from which Shopify will fetch the value:
29
+ # asset.src = "http://mysite.com/image.png"
30
+ #
31
+ # * set a source key of another of your assets from which
32
+ # the value will be copied:
33
+ # asset.source_key = "assets/another_image.png"
34
+ class Asset < Base
35
+ self.primary_key = 'key'
36
+ self.prefix = "/admin/themes/:theme_id/"
37
+
38
+ def self.prefix(options={})
39
+ options[:theme_id].nil? ? "/admin/" : "/admin/themes/#{options[:theme_id]}/"
40
+ end
41
+
42
+ def self.element_path(id, prefix_options = {}, query_options = nil) #:nodoc:
43
+ prefix_options, query_options = split_options(prefix_options) if query_options.nil?
44
+ "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
45
+ end
46
+
47
+ # find an asset by key:
48
+ # ShopifyAPI::Asset.find('layout/theme.liquid', :params => {:theme_id => 99})
49
+ def self.find(*args)
50
+ if args[0].is_a?(Symbol)
51
+ super
52
+ else
53
+ params = {:asset => {:key => args[0]}}
54
+ params = params.merge(args[1][:params]) if args[1] && args[1][:params]
55
+ path_prefix = params[:theme_id] ? "/admin/themes/#{params[:theme_id]}" : "/admin"
56
+ find(:one, :from => "#{path_prefix}/assets.#{format.extension}", :params => params)
57
+ end
58
+ end
59
+
60
+ # For text assets, Shopify returns the data in the 'value' attribute.
61
+ # For binary assets, the data is base-64-encoded and returned in the
62
+ # 'attachment' attribute. This accessor returns the data in both cases.
63
+ def value
64
+ attributes['value'] ||
65
+ (attributes['attachment'] ? Base64.decode64(attributes['attachment']) : nil)
66
+ end
67
+
68
+ def attach(data)
69
+ self.attachment = Base64.encode64(data)
70
+ end
71
+
72
+ def destroy
73
+ connection.delete(element_path(prefix_options.merge(:asset => {:key => key})), self.class.headers)
74
+ end
75
+
76
+ def new?
77
+ false
78
+ end
79
+
80
+ def method_missing(method_symbol, *arguments) #:nodoc:
81
+ if %w{value= attachment= src= source_key=}.include?(method_symbol)
82
+ wipe_value_attributes
83
+ end
84
+ super
85
+ end
86
+
87
+ private
88
+
89
+ def wipe_value_attributes
90
+ %w{value attachment src source_key}.each do |attr|
91
+ attributes.delete(attr)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,6 @@
1
+ module ShopifyAPI
2
+ class Base < ActiveResource::Base
3
+ extend Countable
4
+ self.include_root_in_json = false
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class BillingAddress < Base
3
+ end
4
+ end
@@ -0,0 +1,10 @@
1
+ module ShopifyAPI
2
+ class Blog < Base
3
+ include Events
4
+ include Metafields
5
+
6
+ def articles
7
+ Article.find(:all, :params => { :blog_id => id })
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module ShopifyAPI
2
+ # For adding/removing products from custom collections
3
+ class Collect < Base
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ module ShopifyAPI
2
+ class Comment < Base
3
+ def remove; load_attributes_from_response(post(:remove, {}, only_id)); end
4
+ def spam; load_attributes_from_response(post(:spam, {}, only_id)); end
5
+ def approve; load_attributes_from_response(post(:approve, {}, only_id)); end
6
+ def restore; load_attributes_from_response(post(:restore, {}, only_id)); end
7
+ def not_spam; load_attributes_from_response(post(:not_spam, {}, only_id)); end
8
+
9
+ def only_id
10
+ encode(:only => :id)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class Country < Base
3
+ end
4
+ end
@@ -0,0 +1,19 @@
1
+ module ShopifyAPI
2
+ class CustomCollection < Base
3
+ include Events
4
+ include Metafields
5
+
6
+ def products
7
+ Product.find(:all, :params => {:collection_id => self.id})
8
+ end
9
+
10
+ def add_product(product)
11
+ Collect.create(:collection_id => self.id, :product_id => product.id)
12
+ end
13
+
14
+ def remove_product(product)
15
+ collect = Collect.find(:first, :params => {:collection_id => self.id, :product_id => product.id})
16
+ collect.destroy if collect
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class Customer < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class CustomerGroup < Base
3
+ end
4
+ end