th_shopify_api 1.2.6.pre

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 (65) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/CHANGELOG +57 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +60 -0
  6. data/RELEASING +16 -0
  7. data/Rakefile +41 -0
  8. data/bin/shopify +4 -0
  9. data/lib/active_resource/connection_ext.rb +16 -0
  10. data/lib/shopify_api.rb +18 -0
  11. data/lib/shopify_api/countable.rb +7 -0
  12. data/lib/shopify_api/events.rb +7 -0
  13. data/lib/shopify_api/limits.rb +76 -0
  14. data/lib/shopify_api/metafields.rb +18 -0
  15. data/lib/shopify_api/resources.rb +40 -0
  16. data/lib/shopify_api/resources/address.rb +4 -0
  17. data/lib/shopify_api/resources/application_charge.rb +9 -0
  18. data/lib/shopify_api/resources/article.rb +12 -0
  19. data/lib/shopify_api/resources/asset.rb +95 -0
  20. data/lib/shopify_api/resources/base.rb +5 -0
  21. data/lib/shopify_api/resources/billing_address.rb +4 -0
  22. data/lib/shopify_api/resources/blog.rb +10 -0
  23. data/lib/shopify_api/resources/cli.rb +161 -0
  24. data/lib/shopify_api/resources/collect.rb +5 -0
  25. data/lib/shopify_api/resources/comment.rb +13 -0
  26. data/lib/shopify_api/resources/countable.rb +7 -0
  27. data/lib/shopify_api/resources/country.rb +4 -0
  28. data/lib/shopify_api/resources/custom_collection.rb +19 -0
  29. data/lib/shopify_api/resources/customer.rb +4 -0
  30. data/lib/shopify_api/resources/customer_group.rb +4 -0
  31. data/lib/shopify_api/resources/event.rb +10 -0
  32. data/lib/shopify_api/resources/fulfillment.rb +5 -0
  33. data/lib/shopify_api/resources/image.rb +16 -0
  34. data/lib/shopify_api/resources/line_item.rb +4 -0
  35. data/lib/shopify_api/resources/metafield.rb +15 -0
  36. data/lib/shopify_api/resources/note_attribute.rb +4 -0
  37. data/lib/shopify_api/resources/option.rb +4 -0
  38. data/lib/shopify_api/resources/order.rb +25 -0
  39. data/lib/shopify_api/resources/page.rb +6 -0
  40. data/lib/shopify_api/resources/payment_details.rb +4 -0
  41. data/lib/shopify_api/resources/product.rb +33 -0
  42. data/lib/shopify_api/resources/product_search_engine.rb +4 -0
  43. data/lib/shopify_api/resources/province.rb +5 -0
  44. data/lib/shopify_api/resources/receipt.rb +4 -0
  45. data/lib/shopify_api/resources/recurring_application_charge.rb +23 -0
  46. data/lib/shopify_api/resources/redirect.rb +4 -0
  47. data/lib/shopify_api/resources/rule.rb +4 -0
  48. data/lib/shopify_api/resources/script_tag.rb +4 -0
  49. data/lib/shopify_api/resources/shipping_address.rb +4 -0
  50. data/lib/shopify_api/resources/shipping_line.rb +4 -0
  51. data/lib/shopify_api/resources/shop.rb +23 -0
  52. data/lib/shopify_api/resources/smart_collection.rb +10 -0
  53. data/lib/shopify_api/resources/tax_line.rb +4 -0
  54. data/lib/shopify_api/resources/theme.rb +4 -0
  55. data/lib/shopify_api/resources/transaction.rb +5 -0
  56. data/lib/shopify_api/resources/variant.rb +11 -0
  57. data/lib/shopify_api/resources/webhook.rb +4 -0
  58. data/lib/shopify_api/session.rb +166 -0
  59. data/shopify_api.gemspec +35 -0
  60. data/test/cli_test.rb +109 -0
  61. data/test/limits_test.rb +37 -0
  62. data/test/order_test.rb +48 -0
  63. data/test/shopify_api_test.rb +55 -0
  64. data/test/test_helper.rb +29 -0
  65. metadata +153 -0
@@ -0,0 +1,5 @@
1
+ module ShopifyAPI
2
+ class Base < ActiveResource::Base
3
+ extend Countable
4
+ end
5
+ 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,161 @@
1
+ require 'thor'
2
+ require 'abbrev'
3
+
4
+ module ShopifyAPI
5
+ class Cli < Thor
6
+ include Thor::Actions
7
+
8
+ class ConfigFileError < StandardError
9
+ end
10
+
11
+ desc "list", "list available connections"
12
+ def list
13
+ available_connections.each do |c|
14
+ prefix = default?(c) ? " * " : " "
15
+ puts prefix + c
16
+ end
17
+ end
18
+
19
+ desc "add CONNECTION", "create a config file for a connection named CONNECTION"
20
+ def add(connection)
21
+ file = config_file(connection)
22
+ if File.exist?(file)
23
+ raise ConfigFileError, "There is already a config file at #{file}"
24
+ else
25
+ config = {'protocol' => 'https'}
26
+ config['domain'] = ask("Domain? (leave blank for #{connection}.myshopify.com)")
27
+ config['domain'] = "#{connection}.myshopify.com" if config['domain'].blank?
28
+ puts "\nopen https://#{config['domain']}/admin/api in your browser to get API credentials\n"
29
+ config['api_key'] = ask("API key?")
30
+ config['password'] = ask("Password?")
31
+ create_file(file, config.to_yaml)
32
+ end
33
+ if available_connections.one?
34
+ default(connection)
35
+ end
36
+ end
37
+
38
+ desc "remove CONNECTION", "remove the config file for CONNECTION"
39
+ def remove(connection)
40
+ file = config_file(connection)
41
+ if File.exist?(file)
42
+ remove_file(default_symlink) if default?(connection)
43
+ remove_file(file)
44
+ else
45
+ no_config_file_error(file)
46
+ end
47
+ end
48
+
49
+ desc "edit [CONNECTION]", "open the config file for CONNECTION with your default editor"
50
+ def edit(connection=nil)
51
+ file = config_file(connection)
52
+ if File.exist?(file)
53
+ if ENV['EDITOR'].present?
54
+ system(ENV['EDITOR'], file)
55
+ else
56
+ puts "Please set an editor in the EDITOR environment variable"
57
+ end
58
+ else
59
+ no_config_file_error(file)
60
+ end
61
+ end
62
+
63
+ desc "show [CONNECTION]", "output the location and contents of the CONNECTION's config file"
64
+ def show(connection=nil)
65
+ connection ||= default_connection
66
+ file = config_file(connection)
67
+ if File.exist?(file)
68
+ puts file
69
+ puts `cat #{file}`
70
+ else
71
+ no_config_file_error(file)
72
+ end
73
+ end
74
+
75
+ desc "default [CONNECTION]", "show the default connection, or make CONNECTION the default"
76
+ def default(connection=nil)
77
+ if connection
78
+ target = config_file(connection)
79
+ if File.exist?(target)
80
+ remove_file(default_symlink)
81
+ `ln -s #{target} #{default_symlink}`
82
+ else
83
+ no_config_file_error(target)
84
+ end
85
+ end
86
+ if File.exist?(default_symlink)
87
+ puts "Default connection is #{default_connection}"
88
+ else
89
+ puts "There is no default connection set"
90
+ end
91
+ end
92
+
93
+ desc "console [CONNECTION]", "start an API console for CONNECTION"
94
+ def console(connection=nil)
95
+ file = config_file(connection)
96
+
97
+ config = YAML.load(File.read(file))
98
+ puts "using #{config['domain']}"
99
+ ShopifyAPI::Base.site = site_from_config(config)
100
+
101
+ require 'irb'
102
+ require 'irb/completion'
103
+ ARGV.clear
104
+ IRB.start
105
+ end
106
+
107
+ tasks.keys.abbrev.each do |shortcut, command|
108
+ map shortcut => command.to_sym
109
+ end
110
+
111
+ private
112
+
113
+ def shop_config_dir
114
+ @shop_config_dir ||= File.join(ENV['HOME'], '.shopify', 'shops')
115
+ end
116
+
117
+ def default_symlink
118
+ @default_symlink ||= File.join(shop_config_dir, 'default')
119
+ end
120
+
121
+ def config_file(connection)
122
+ if connection
123
+ File.join(shop_config_dir, "#{connection}.yml")
124
+ else
125
+ default_symlink
126
+ end
127
+ end
128
+
129
+ def site_from_config(config)
130
+ protocol = config['protocol'] || 'https'
131
+ api_key = config['api_key']
132
+ password = config['password']
133
+ domain = config['domain']
134
+
135
+ ShopifyAPI::Base.site = "#{protocol}://#{api_key}:#{password}@#{domain}/admin"
136
+ end
137
+
138
+ def available_connections
139
+ @available_connections ||= begin
140
+ pattern = File.join(shop_config_dir, "*.yml")
141
+ Dir.glob(pattern).map { |f| File.basename(f, ".yml") }
142
+ end
143
+ end
144
+
145
+ def default_connection_target
146
+ @default_connection_target ||= File.readlink(default_symlink)
147
+ end
148
+
149
+ def default_connection
150
+ @default_connection ||= File.basename(default_connection_target, ".yml")
151
+ end
152
+
153
+ def default?(connection)
154
+ default_connection == connection
155
+ end
156
+
157
+ def no_config_file_error(filename)
158
+ raise ConfigFileError, "There is no config file at #{filename}"
159
+ end
160
+ end
161
+ 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,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,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
@@ -0,0 +1,10 @@
1
+ module ShopifyAPI
2
+ class Event < Base
3
+ self.prefix = "/admin/:resource/:resource_id/"
4
+
5
+ # Hack to allow both Shop and other Events in through the same AR class
6
+ def self.prefix(options={})
7
+ options[:resource].nil? ? "/admin/" : "/admin/#{options[:resource]}/#{options[:resource_id]}/"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module ShopifyAPI
2
+ class Fulfillment < Base
3
+ self.prefix = "/admin/orders/:order_id/"
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ module ShopifyAPI
2
+ class Image < Base
3
+ self.prefix = "/admin/products/:product_id/"
4
+
5
+ # generate a method for each possible image variant
6
+ [:pico, :icon, :thumb, :small, :compact, :medium, :large, :grande, :original].each do |m|
7
+ reg_exp_match = "/\\1_#{m}.\\2"
8
+ define_method(m) { src.gsub(/\/(.*)\.(\w{2,4})/, reg_exp_match) }
9
+ end
10
+
11
+ def attach_image(data, filename = nil)
12
+ attributes['attachment'] = Base64.encode64(data)
13
+ attributes['filename'] = filename unless filename.nil?
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class LineItem < Base
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ module ShopifyAPI
2
+ class Metafield < Base
3
+ self.prefix = "/admin/:resource/:resource_id/"
4
+
5
+ # Hack to allow both Shop and other Metafields in through the same AR class
6
+ def self.prefix(options={})
7
+ options[:resource].nil? ? "/admin/" : "/admin/#{options[:resource]}/#{options[:resource_id]}/"
8
+ end
9
+
10
+ def value
11
+ return if attributes["value"].nil?
12
+ attributes["value_type"] == "integer" ? attributes["value"].to_i : attributes["value"]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class NoteAttribute < Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class Option < Base
3
+ end
4
+ end
@@ -0,0 +1,25 @@
1
+ module ShopifyAPI
2
+ class Order < Base
3
+ include Events
4
+ include Metafields
5
+
6
+ def close; load_attributes_from_response(post(:close, {}, only_id)); end
7
+ def open; load_attributes_from_response(post(:open, {}, only_id)); end
8
+
9
+ def cancel(options = {})
10
+ load_attributes_from_response(post(:cancel, options, only_id))
11
+ end
12
+
13
+ def transactions
14
+ Transaction.find(:all, :params => { :order_id => id })
15
+ end
16
+
17
+ def capture(amount = "")
18
+ Transaction.create(:amount => amount, :kind => "capture", :order_id => id)
19
+ end
20
+
21
+ def only_id
22
+ encode(:only => :id, :include => [], :methods => [], :fields => [])
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ module ShopifyAPI
2
+ class Page < Base
3
+ include Events
4
+ include Metafields
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class PaymentDetails < Base
3
+ end
4
+ end
@@ -0,0 +1,33 @@
1
+ module ShopifyAPI
2
+ class Product < Base
3
+ include Events
4
+ include Metafields
5
+
6
+ # compute the price range
7
+ def price_range
8
+ prices = variants.collect(&:price)
9
+ format = "%0.2f"
10
+ if prices.min != prices.max
11
+ "#{format % prices.min} - #{format % prices.max}"
12
+ else
13
+ format % prices.min
14
+ end
15
+ end
16
+
17
+ def collections
18
+ CustomCollection.find(:all, :params => {:product_id => self.id})
19
+ end
20
+
21
+ def smart_collections
22
+ SmartCollection.find(:all, :params => {:product_id => self.id})
23
+ end
24
+
25
+ def add_to_collection(collection)
26
+ collection.add_product(self)
27
+ end
28
+
29
+ def remove_from_collection(collection)
30
+ collection.remove_product(self)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class ProductSearchEngine < Base
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module ShopifyAPI
2
+ class Province < Base
3
+ self.prefix = "/admin/countries/:country_id/"
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class Receipt < Base
3
+ end
4
+ end
@@ -0,0 +1,23 @@
1
+ module ShopifyAPI
2
+ class RecurringApplicationCharge < Base
3
+ undef_method :test
4
+
5
+ class << self
6
+ def current
7
+ all.find { |c| c.status == 'active' }
8
+ end
9
+
10
+ [:pending, :cancelled, :accepted, :declined].each do |status|
11
+ define_method(status) { all.select { |c| c.status == status.to_s } }
12
+ end
13
+ end
14
+
15
+ def cancel
16
+ load_attributes_from_response(self.destroy)
17
+ end
18
+
19
+ def activate
20
+ load_attributes_from_response(post(:activate))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyAPI
2
+ class Redirect < Base
3
+ end
4
+ end