gecko-ruby 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rubocop.yml +9 -0
  4. data/.travis.yml +5 -0
  5. data/CHANGELOG.md +41 -0
  6. data/CONTRIBUTING.md +0 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +144 -0
  10. data/Rakefile +19 -0
  11. data/gecko-ruby.gemspec +34 -0
  12. data/generate.thor +81 -0
  13. data/lib/gecko-ruby.rb +1 -0
  14. data/lib/gecko.rb +31 -0
  15. data/lib/gecko/client.rb +132 -0
  16. data/lib/gecko/helpers/association_helper.rb +69 -0
  17. data/lib/gecko/helpers/inspection_helper.rb +51 -0
  18. data/lib/gecko/helpers/record_helper.rb +29 -0
  19. data/lib/gecko/helpers/serialization_helper.rb +61 -0
  20. data/lib/gecko/helpers/validation_helper.rb +91 -0
  21. data/lib/gecko/record/account.rb +66 -0
  22. data/lib/gecko/record/address.rb +33 -0
  23. data/lib/gecko/record/base.rb +66 -0
  24. data/lib/gecko/record/base_adapter.rb +365 -0
  25. data/lib/gecko/record/company.rb +41 -0
  26. data/lib/gecko/record/contact.rb +25 -0
  27. data/lib/gecko/record/currency.rb +23 -0
  28. data/lib/gecko/record/exceptions.rb +15 -0
  29. data/lib/gecko/record/fulfillment.rb +42 -0
  30. data/lib/gecko/record/fulfillment_line_item.rb +26 -0
  31. data/lib/gecko/record/image.rb +38 -0
  32. data/lib/gecko/record/invoice.rb +29 -0
  33. data/lib/gecko/record/invoice_line_item.rb +18 -0
  34. data/lib/gecko/record/location.rb +23 -0
  35. data/lib/gecko/record/order.rb +50 -0
  36. data/lib/gecko/record/order_line_item.rb +35 -0
  37. data/lib/gecko/record/product.rb +27 -0
  38. data/lib/gecko/record/purchase_order.rb +43 -0
  39. data/lib/gecko/record/purchase_order_line_item.rb +29 -0
  40. data/lib/gecko/record/tax_type.rb +21 -0
  41. data/lib/gecko/record/user.rb +44 -0
  42. data/lib/gecko/record/variant.rb +96 -0
  43. data/lib/gecko/version.rb +3 -0
  44. data/test/client_test.rb +29 -0
  45. data/test/fixtures/vcr_cassettes/accounts.yml +57 -0
  46. data/test/fixtures/vcr_cassettes/accounts_current.yml +57 -0
  47. data/test/fixtures/vcr_cassettes/addresses.yml +68 -0
  48. data/test/fixtures/vcr_cassettes/addresses_count.yml +58 -0
  49. data/test/fixtures/vcr_cassettes/companies.yml +62 -0
  50. data/test/fixtures/vcr_cassettes/companies_count.yml +58 -0
  51. data/test/fixtures/vcr_cassettes/contacts.yml +60 -0
  52. data/test/fixtures/vcr_cassettes/contacts_count.yml +58 -0
  53. data/test/fixtures/vcr_cassettes/currencies.yml +62 -0
  54. data/test/fixtures/vcr_cassettes/currencies_count.yml +58 -0
  55. data/test/fixtures/vcr_cassettes/fulfillments.yml +59 -0
  56. data/test/fixtures/vcr_cassettes/fulfillments_count.yml +58 -0
  57. data/test/fixtures/vcr_cassettes/images.yml +59 -0
  58. data/test/fixtures/vcr_cassettes/images_count.yml +58 -0
  59. data/test/fixtures/vcr_cassettes/invoice_line_items.yml +63 -0
  60. data/test/fixtures/vcr_cassettes/invoice_line_items_count.yml +62 -0
  61. data/test/fixtures/vcr_cassettes/invoices.yml +63 -0
  62. data/test/fixtures/vcr_cassettes/invoices_count.yml +62 -0
  63. data/test/fixtures/vcr_cassettes/locations.yml +65 -0
  64. data/test/fixtures/vcr_cassettes/locations_count.yml +58 -0
  65. data/test/fixtures/vcr_cassettes/order_line_items.yml +63 -0
  66. data/test/fixtures/vcr_cassettes/order_line_items_count.yml +62 -0
  67. data/test/fixtures/vcr_cassettes/orders.yml +62 -0
  68. data/test/fixtures/vcr_cassettes/orders_count.yml +58 -0
  69. data/test/fixtures/vcr_cassettes/products.yml +79 -0
  70. data/test/fixtures/vcr_cassettes/products_count.yml +58 -0
  71. data/test/fixtures/vcr_cassettes/products_new_invalid.yml +54 -0
  72. data/test/fixtures/vcr_cassettes/products_new_valid.yml +58 -0
  73. data/test/fixtures/vcr_cassettes/purchase_order_line_items.yml +64 -0
  74. data/test/fixtures/vcr_cassettes/purchase_order_line_items_count.yml +62 -0
  75. data/test/fixtures/vcr_cassettes/purchase_orders.yml +63 -0
  76. data/test/fixtures/vcr_cassettes/purchase_orders_count.yml +62 -0
  77. data/test/fixtures/vcr_cassettes/tax_types.yml +74 -0
  78. data/test/fixtures/vcr_cassettes/tax_types_count.yml +62 -0
  79. data/test/fixtures/vcr_cassettes/users.yml +60 -0
  80. data/test/fixtures/vcr_cassettes/users_count.yml +58 -0
  81. data/test/fixtures/vcr_cassettes/users_current.yml +54 -0
  82. data/test/fixtures/vcr_cassettes/variants.yml +60 -0
  83. data/test/fixtures/vcr_cassettes/variants_count.yml +58 -0
  84. data/test/gecko_test.rb +7 -0
  85. data/test/helpers/association_helper_test.rb +56 -0
  86. data/test/helpers/inspection_helper_test.rb +27 -0
  87. data/test/helpers/serialization_helper_test.rb +30 -0
  88. data/test/helpers/validation_helper_test.rb +24 -0
  89. data/test/record/account_adapter_test.rb +43 -0
  90. data/test/record/address_adapter_test.rb +14 -0
  91. data/test/record/address_test.rb +18 -0
  92. data/test/record/company_adapter_test.rb +14 -0
  93. data/test/record/company_test.rb +18 -0
  94. data/test/record/contact_adapter_test.rb +14 -0
  95. data/test/record/contact_test.rb +18 -0
  96. data/test/record/currency_adapter_test.rb +14 -0
  97. data/test/record/currency_test.rb +18 -0
  98. data/test/record/fulfillment_adapter_test.rb +24 -0
  99. data/test/record/fulfillment_line_item_adapter_test.rb +21 -0
  100. data/test/record/fulfillment_line_item_test.rb +18 -0
  101. data/test/record/fulfillment_test.rb +27 -0
  102. data/test/record/image_adapter_test.rb +14 -0
  103. data/test/record/image_test.rb +25 -0
  104. data/test/record/invoice_adapter_test.rb +14 -0
  105. data/test/record/invoice_line_item_adapter_test.rb +20 -0
  106. data/test/record/invoice_line_item_test.rb +18 -0
  107. data/test/record/invoice_test.rb +18 -0
  108. data/test/record/location_adapter_test.rb +14 -0
  109. data/test/record/location_test.rb +18 -0
  110. data/test/record/order_adapter_test.rb +14 -0
  111. data/test/record/order_line_item_adapter_test.rb +14 -0
  112. data/test/record/order_line_item_test.rb +18 -0
  113. data/test/record/order_test.rb +18 -0
  114. data/test/record/product_adapter_test.rb +32 -0
  115. data/test/record/product_test.rb +18 -0
  116. data/test/record/purchase_order_adapter_test.rb +14 -0
  117. data/test/record/purchase_order_line_item_adapter_test.rb +14 -0
  118. data/test/record/purchase_order_line_item_test.rb +18 -0
  119. data/test/record/purchase_order_test.rb +18 -0
  120. data/test/record/tax_type_adapter_test.rb +14 -0
  121. data/test/record/tax_type_test.rb +18 -0
  122. data/test/record/user_adapter_test.rb +27 -0
  123. data/test/record/user_test.rb +18 -0
  124. data/test/record/variant_adapter_test.rb +14 -0
  125. data/test/record/variant_test.rb +44 -0
  126. data/test/support/let.rb +10 -0
  127. data/test/support/shared_adapter_examples.rb +159 -0
  128. data/test/support/shared_record_examples.rb +21 -0
  129. data/test/support/testing_adapter.rb +11 -0
  130. data/test/support/vcr_support.rb +7 -0
  131. data/test/test_helper.rb +21 -0
  132. metadata +430 -0
@@ -0,0 +1,69 @@
1
+ module Gecko
2
+ module Helpers
3
+ # Helper for has_many/belongs_to relationships
4
+ module AssociationHelper
5
+ # Set up a belongs_to relationship between two classes based on a
6
+ # JSON key of {class_name}_id.
7
+ #
8
+ # @example
9
+ # class Gecko::Record::Variant
10
+ # belongs_to :product
11
+ # end
12
+ #
13
+ # @param [Symbol] model_name
14
+ # @param [#to_hash] options options for setting up the relationship
15
+ # @option options [String] :class_name Override the default class name
16
+ # @option options [String] :readonly (false) Whether to serialize
17
+ # this attribute
18
+ #
19
+ # @return [undefined]
20
+ #
21
+ # @api public
22
+ def belongs_to(model_name, options={})
23
+ class_name = options[:class_name] || model_name.to_s.classify
24
+ foreign_key = model_name.to_s.foreign_key.to_sym
25
+
26
+ define_method model_name do
27
+ if (id = attributes[foreign_key])
28
+ @client.adapter_for(class_name).find(id)
29
+ end
30
+ end
31
+ attribute foreign_key, Integer, readonly: options[:readonly]
32
+ end
33
+
34
+ # Set up a has_many relationship between two classes based on a
35
+ # JSON key of {class_name}_ids.
36
+ #
37
+ # @example
38
+ # class Gecko::Record::Product
39
+ # has_many :variants
40
+ # end
41
+ #
42
+ # @param [Symbol] model_name
43
+ # @param [#to_hash] options options for setting up the relationship
44
+ # @option options [String] :class_name Override the default class name
45
+ # @option options [String] :readonly (true) Whether to serialize this
46
+ # attribute
47
+ #
48
+ # @return [undefined]
49
+ #
50
+ # @api public
51
+ def has_many(association_name, options={})
52
+ model_name = association_name.to_s.singularize
53
+ class_name = options[:class_name] || model_name.classify
54
+ foreign_key = model_name.foreign_key.pluralize.to_sym
55
+ readonly = options.key?(:readonly) ? options[:readonly] : true
56
+
57
+ define_method association_name do
58
+ ids = self.attributes[foreign_key]
59
+ if ids.any?
60
+ @client.adapter_for(class_name).find_many(ids)
61
+ else
62
+ []
63
+ end
64
+ end
65
+ attribute foreign_key, Array[Integer], readonly: readonly
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,51 @@
1
+ module Gecko
2
+ module Helpers
3
+ # Helper for providing custom #inspect values to Records
4
+ module InspectionHelper
5
+ # Overrides the default inspect to just return the defined attributes
6
+ #
7
+ # @example
8
+ # company.inspect #=> <Gecko::Record::Company id=123 name="Gecko Inc">
9
+ #
10
+ # @return [String]
11
+ #
12
+ # @api public
13
+ def inspect
14
+ inspection = self.class.attribute_set.map do |attribute|
15
+ name = attribute.name
16
+ "#{name}: #{attribute_for_inspect(name)}"
17
+ end.join(', ')
18
+ "#<#{self.class} #{inspection}>"
19
+ end
20
+
21
+ private
22
+
23
+ # Returns an #inspect-like string for the value of the attribute
24
+ # attr_name.
25
+ # String attributes are truncated up to 50 characters,
26
+ # and Date and Time attributes are returned in the :db format.
27
+ # Other attributes return the value of #inspect without modification.
28
+ # Duplicated from Rails implementation
29
+ #
30
+ # @example
31
+ # company.inspect #=> <Gecko::Record::Company id=123 name="Gecko Inc">
32
+ #
33
+ # @return [String]
34
+ #
35
+ # @api private
36
+ def attribute_for_inspect(attr_name)
37
+ value = attributes[attr_name]
38
+
39
+ if value.is_a?(String) && value.length > 50
40
+ "#{value[0..50]}...".inspect
41
+ elsif value.is_a?(DateTime) || value.is_a?(Time)
42
+ %("#{value.strftime("%Y-%m-%d %H:%M:%S")}")
43
+ elsif value.is_a?(Date)
44
+ %("#{value.strftime("%Y-%m-%d")}")
45
+ else
46
+ value.inspect
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,29 @@
1
+ module Gecko
2
+ module Helpers
3
+ # Helper for registering valid record types
4
+ module RecordHelper
5
+ # Registers a record type on the Gecko::Client
6
+ #
7
+ # @example
8
+ # class Gecko::Client
9
+ # record :Invoice
10
+ # end
11
+ #
12
+ # @return [undefined]
13
+ #
14
+ # @api private
15
+ def record(record_type)
16
+ define_method record_type do
17
+ adapter_cache = "@#{record_type}_cache".to_sym
18
+ unless instance_variable_defined?(adapter_cache)
19
+ adapter_name = "#{record_type}Adapter".to_sym
20
+ adapter_klass = Gecko::Record.const_get(adapter_name)
21
+ adapter = adapter_klass.new(self, record_type)
22
+ instance_variable_set(adapter_cache, adapter)
23
+ end
24
+ instance_variable_get(adapter_cache)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ module Gecko
2
+ module Helpers
3
+ # Provides serialization to records
4
+ module SerializationHelper
5
+ # Returns a full JSON representation of a record
6
+ #
7
+ # @example
8
+ # product.as_json #=> {product: {id: 12, name: "Big"}}
9
+ #
10
+ # @return [Hash]
11
+ #
12
+ # @api private
13
+ def as_json
14
+ {
15
+ root => serializable_hash
16
+ }
17
+ end
18
+
19
+ # Return a serialized hash of the record's attributes
20
+ #
21
+ # @example
22
+ # product.serializable_hash #=> {id: 12, name: "Big"}
23
+ #
24
+ # @return [Hash]
25
+ #
26
+ # @api private
27
+ def serializable_hash
28
+ attribute_hash = {}
29
+ attribute_set.each do |attribute|
30
+ next if attribute.options[:readonly]
31
+ serialize_attribute(attribute_hash, attribute)
32
+ end
33
+ attribute_hash
34
+ end
35
+
36
+ # Serialize a single attribute
37
+ #
38
+ # @param [Hash] attribute_hash Serialized record being iterated over
39
+ # @param [Virtus::Attribute] attribute The attribute being serialized
40
+ #
41
+ # @return [undefined]
42
+ #
43
+ # @api private
44
+ def serialize_attribute(attribute_hash, attribute)
45
+ attribute_hash[attribute.name] = attributes[attribute.name]
46
+ end
47
+
48
+ # Return JSON root key for a record
49
+ #
50
+ # @example
51
+ # product.root #=> "product"
52
+ #
53
+ # @return [String]
54
+ #
55
+ # @api private
56
+ def root
57
+ self.class.demodulized_name.underscore.to_sym
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,91 @@
1
+ module Gecko
2
+ module Helpers
3
+ # Provides record validation
4
+ module ValidationHelper
5
+ # Returns whether a record is considered valid or not
6
+ #
7
+ # @return [Boolean]
8
+ #
9
+ # @api public
10
+ def valid?
11
+ errors.empty?
12
+ end
13
+
14
+ # Returns the validation errors object for the record
15
+ #
16
+ # @return [Gecko::Errors]
17
+ #
18
+ # @api public
19
+ def errors
20
+ @errors ||= Gecko::Errors.new(self)
21
+ end
22
+ end
23
+ end
24
+
25
+ class Errors
26
+ # The hash of errors for this record
27
+ #
28
+ # @return [Hash]
29
+ #
30
+ # @api public
31
+ attr_reader :messages
32
+
33
+ # Set up the error object
34
+ #
35
+ # @api private
36
+ def initialize(base)
37
+ @base = base
38
+ @messages = {}
39
+ end
40
+
41
+ # Fetch the errors for a specific attribute
42
+ #
43
+ # @example
44
+ # product.errors[:name]
45
+ # #=> ["can't be blank"]
46
+ #
47
+ # @params [Symbol] :attribute
48
+ #
49
+ # @return [Array]
50
+ #
51
+ # @api public
52
+ def [](attribute)
53
+ messages[attribute.to_sym]
54
+ end
55
+
56
+ # Clear the validation errors
57
+ #
58
+ # @example
59
+ # product.errors.clear
60
+ #
61
+ # @return [undefined]
62
+ #
63
+ # @api public
64
+ def clear
65
+ @messages.clear
66
+ end
67
+
68
+ # Whether there are any errors
69
+ #
70
+ # @return [Boolean]
71
+ #
72
+ # @api public
73
+ def empty?
74
+ messages.all? { |k, v| v && v.empty? && !v.is_a?(String) }
75
+ end
76
+
77
+ # Parse JSON errors response into the error object
78
+ #
79
+ # @params [#to_hash] :error_hash hash of errors where key is an attribute
80
+ # name and value is an array of error strings
81
+ #
82
+ # @return [undefined]
83
+ #
84
+ # @api private
85
+ def from_response(error_hash)
86
+ error_hash.each do |attr, errors|
87
+ @messages[attr.to_sym] = errors
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,66 @@
1
+ require 'gecko/record/base'
2
+
3
+ module Gecko
4
+ module Record
5
+ class Account < Base
6
+ has_many :users
7
+ has_many :locations
8
+
9
+ attribute :name, String
10
+ attribute :industry, String
11
+ attribute :logo_url, String, readonly: true
12
+ attribute :website, String
13
+ attribute :tax_number, String
14
+ attribute :country, String
15
+ attribute :time_zone, String
16
+
17
+ attribute :contact_email, String
18
+ attribute :contact_mobile, String
19
+ attribute :contact_phone, String
20
+
21
+ attribute :invoice_details, String
22
+ attribute :order_details, String
23
+ attribute :quote_details, String
24
+
25
+ attribute :default_tax_rate, String
26
+
27
+ attribute :default_tax_type_id, Integer
28
+
29
+ attribute :tax_number_label, String
30
+ attribute :tax_label, String
31
+
32
+ belongs_to :billing_contact, class_name: "User"
33
+ belongs_to :primary_location, class_name: "Location"
34
+ belongs_to :primary_billing_location, class_name: "Location"
35
+
36
+ # belongs_to :default_currency, class_name: "Currency"
37
+ # belongs_to :default_payment_term, class_name: "PaymentTerm"
38
+ # belongs_to :default_purchase_order_price_list, class_name: "PriceList"
39
+ # belongs_to :default_order_price_list, class_name: "PriceList"
40
+
41
+ # attribute :stock_level_warn, String
42
+ # attribute :subscription_name, String
43
+ # attribute :subscription_price, String
44
+ # attribute :annual_subscription_price, String
45
+ # attribute :expires_at, String
46
+ end
47
+
48
+ class AccountAdapter < BaseAdapter
49
+ undef :count
50
+ undef :build
51
+
52
+ # Return the account for the logged in user
53
+ #
54
+ # @return [Gecko::Record::Account]
55
+ #
56
+ # @api public
57
+ def current
58
+ if self.has_record_for_id?(:current)
59
+ record_for_id(:current)
60
+ else
61
+ @identity_map[:current] = find(:current)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,33 @@
1
+ require 'gecko/record/base'
2
+
3
+ module Gecko
4
+ module Record
5
+ class Address < Base
6
+ belongs_to :company
7
+
8
+ attribute :phone_number, String
9
+ attribute :email, String
10
+
11
+ attribute :label, String
12
+ attribute :company_name, String
13
+ attribute :address1, String
14
+ attribute :address2, String
15
+ attribute :suburb, String
16
+ attribute :city, String
17
+ attribute :state, String
18
+ attribute :country, String
19
+ attribute :zip_code, String
20
+
21
+ attribute :status, String, readonly: true
22
+
23
+ alias_method :country_code, :country
24
+ end
25
+
26
+ class AddressAdapter < BaseAdapter
27
+ # Override plural_path to properly pluralize address
28
+ def plural_path
29
+ 'addresses'
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,66 @@
1
+ require 'gecko/helpers/association_helper'
2
+ require 'gecko/helpers/inspection_helper'
3
+ require 'gecko/helpers/serialization_helper'
4
+ require 'gecko/helpers/validation_helper'
5
+
6
+ module Gecko
7
+ module Record
8
+ class Base
9
+ include Virtus.model
10
+ extend Gecko::Helpers::AssociationHelper
11
+ include Gecko::Helpers::InspectionHelper
12
+ include Gecko::Helpers::SerializationHelper
13
+ include Gecko::Helpers::ValidationHelper
14
+
15
+ # Set up the default attributes associated with all records
16
+ attribute :id, Integer, readonly: true
17
+ attribute :updated_at, DateTime, readonly: true
18
+ attribute :created_at, DateTime, readonly: true
19
+
20
+ # Overrides the default Virtus functionality to store:
21
+ # - The Gecko::Client used to create the object
22
+ # - a raw copy of the attributes for the association helpers to read from
23
+ #
24
+ # @return [undefined]
25
+ #
26
+ # @api private
27
+ def initialize(client, attributes={})
28
+ super(attributes)
29
+ @client = client
30
+ end
31
+
32
+ # Whether the record has been persisted
33
+ #
34
+ # @example
35
+ # variant.persisted? #=> true
36
+ #
37
+ # @return <Boolean>
38
+ #
39
+ # @api public
40
+ def persisted?
41
+ !!id
42
+ end
43
+
44
+ # Save a record
45
+ #
46
+ # @return <Gecko::Record::Base>
47
+ #
48
+ # @api public
49
+ def save
50
+ @client.adapter_for(self.class.demodulized_name).save(self)
51
+ end
52
+
53
+ # Return the demodulized class name
54
+ #
55
+ # @example
56
+ # Gecko::Record::Product.demodulized_name #=> "Product"
57
+ #
58
+ # @return [String]
59
+ #
60
+ # @api private
61
+ def self.demodulized_name
62
+ self.name.split('::').last
63
+ end
64
+ end
65
+ end
66
+ end