cornerstore 0.6.2
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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/README.md +75 -0
- data/Rakefile +1 -0
- data/cornerstore.gemspec +23 -0
- data/examples/cornerstore.yml +11 -0
- data/lib/cornerstore.rb +84 -0
- data/lib/cornerstore/api.rb +18 -0
- data/lib/cornerstore/api/address.rb +12 -0
- data/lib/cornerstore/api/cancellation.rb +22 -0
- data/lib/cornerstore/api/carrier.rb +3 -0
- data/lib/cornerstore/api/cart.rb +49 -0
- data/lib/cornerstore/api/collection.rb +25 -0
- data/lib/cornerstore/api/customer.rb +4 -0
- data/lib/cornerstore/api/differentiating_property.rb +15 -0
- data/lib/cornerstore/api/image.rb +20 -0
- data/lib/cornerstore/api/line_item.rb +74 -0
- data/lib/cornerstore/api/order.rb +94 -0
- data/lib/cornerstore/api/payment_means.rb +32 -0
- data/lib/cornerstore/api/price.rb +77 -0
- data/lib/cornerstore/api/product.rb +72 -0
- data/lib/cornerstore/api/property.rb +30 -0
- data/lib/cornerstore/api/search.rb +35 -0
- data/lib/cornerstore/api/shipment.rb +24 -0
- data/lib/cornerstore/api/variant.rb +49 -0
- data/lib/cornerstore/model.rb +117 -0
- data/lib/cornerstore/resource.rb +8 -0
- data/lib/cornerstore/resource/base.rb +83 -0
- data/lib/cornerstore/resource/filter.rb +21 -0
- data/lib/cornerstore/resource/remote.rb +27 -0
- data/lib/cornerstore/resource/writable.rb +20 -0
- data/lib/cornerstore/version.rb +3 -0
- data/spec/order_spec.rb +78 -0
- data/spec/price_spec.rb +51 -0
- data/spec/spec_helper.rb +22 -0
- metadata +126 -0
| @@ -0,0 +1,94 @@ | |
| 1 | 
            +
            class Cornerstore::Order < Cornerstore::Model::Base
         | 
| 2 | 
            +
              attr_accessor :number,
         | 
| 3 | 
            +
                :reference,
         | 
| 4 | 
            +
                :checkout_id,
         | 
| 5 | 
            +
                :placed_at,
         | 
| 6 | 
            +
                :invoice_number,
         | 
| 7 | 
            +
                :invoiced_at,
         | 
| 8 | 
            +
                :checkout_started_at,
         | 
| 9 | 
            +
                :requested_carrier,
         | 
| 10 | 
            +
                :weight,
         | 
| 11 | 
            +
                :subtotal,
         | 
| 12 | 
            +
                :shipping_costs,
         | 
| 13 | 
            +
                :payment_costs,
         | 
| 14 | 
            +
                :sales_tax,
         | 
| 15 | 
            +
                :total,
         | 
| 16 | 
            +
                :customer_comment,
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                :customer,
         | 
| 19 | 
            +
                :shipping_address,
         | 
| 20 | 
            +
                :billing_address,
         | 
| 21 | 
            +
                :payment_means,
         | 
| 22 | 
            +
                :requested_carrier,
         | 
| 23 | 
            +
                :line_items,
         | 
| 24 | 
            +
                :shipments,
         | 
| 25 | 
            +
                :cancellations
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              def initialize(attributes, parent=nil)
         | 
| 28 | 
            +
                self.payment_costs      = Cornerstore::Price.new(attributes.delete('payment_costs')) if attributes['payment_costs']
         | 
| 29 | 
            +
                self.shipping_costs     = Cornerstore::Price.new(attributes.delete('shipping_costs')) if attributes['shipping_costs']
         | 
| 30 | 
            +
                self.subtotal           = Cornerstore::Price.new(attributes.delete('subtotal'))
         | 
| 31 | 
            +
                self.sales_tax          = Cornerstore::Price.new(attributes.delete('sales_tax')) if attributes['sales_tax']
         | 
| 32 | 
            +
                self.total              = Cornerstore::Price.new(attributes.delete('total'))
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                self.customer           = Cornerstore::Customer.new(attributes.delete('customer'))
         | 
| 35 | 
            +
                self.shipping_address   = Cornerstore::Address.new(attributes.delete('shipping_address'))
         | 
| 36 | 
            +
                self.billing_address    = Cornerstore::Address.new(attributes.delete('billing_address'))
         | 
| 37 | 
            +
                self.payment_means      = Cornerstore::PaymentMeans.new(attributes.delete('payment_means'))
         | 
| 38 | 
            +
                self.requested_carrier  = Cornerstore::Carrier.new(attributes.delete('requested_carrier'))
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                self.line_items         = Cornerstore::LineItem::Resource.new(self, attributes.delete('line_items') || [], 'line_items')
         | 
| 41 | 
            +
                self.shipments          = Cornerstore::Shipment::Resource.new(self, attributes.delete('shipments') || [], 'shipments')
         | 
| 42 | 
            +
                self.cancellations      = Cornerstore::Cancellation::Resource.new(self, attributes.delete('cancellations') || [], 'cancellations')
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                self.placed_at          = DateTime.parse(attributes.delete('placed_at')) unless attributes['placed_at'].blank?
         | 
| 45 | 
            +
                self.invoiced_at        = DateTime.parse(attributes.delete('invoiced_at')) unless attributes['invoiced_at'].blank?
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                super
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def requires_shipment?
         | 
| 51 | 
            +
                self.line_items.any? { |li| li.requires_shipment? }
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              # Returns true if at least one line item of this order is shipped
         | 
| 55 | 
            +
              def shipped?
         | 
| 56 | 
            +
                !self.shipments.empty?
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              # Returns true if at least one line item of this order is canceled
         | 
| 60 | 
            +
              def canceled?
         | 
| 61 | 
            +
                !self.cancellations.empty?
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              def invoiced?
         | 
| 65 | 
            +
                !self.invoice_number.blank?
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              # Returns true if all line items are shipped
         | 
| 69 | 
            +
              def completely_shipped?
         | 
| 70 | 
            +
                self.shipments.collect(&:shipped_items).flatten.size == self.line_items.size
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
              # Returns true if all line items are canceled
         | 
| 74 | 
            +
              def completely_canceled?
         | 
| 75 | 
            +
                self.cancellations.collect(&:canceled_items).flatten.size == self.line_items.size
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              def handling_costs
         | 
| 79 | 
            +
                if self.shipping_costs and self.payment_costs
         | 
| 80 | 
            +
                  self.shipping_costs + self.payment_costs
         | 
| 81 | 
            +
                elsif self.shipping_costs
         | 
| 82 | 
            +
                  self.shipping_costs
         | 
| 83 | 
            +
                elsif self.payment_costs
         | 
| 84 | 
            +
                  self.payment_costs
         | 
| 85 | 
            +
                else
         | 
| 86 | 
            +
                  nil      
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
              def check_signature
         | 
| 91 | 
            +
                # TODO: Implement signature check
         | 
| 92 | 
            +
                return true
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            class Cornerstore::PaymentMeans < Cornerstore::Model::Base
         | 
| 2 | 
            +
              attr_accessor :kind,
         | 
| 3 | 
            +
                            :paid_at,
         | 
| 4 | 
            +
                            :refunded_at,
         | 
| 5 | 
            +
                            :card_token,
         | 
| 6 | 
            +
                            :issuer,
         | 
| 7 | 
            +
                            :last_digits,
         | 
| 8 | 
            +
                            :owner,
         | 
| 9 | 
            +
                            :bank,
         | 
| 10 | 
            +
                            :iban,
         | 
| 11 | 
            +
                            :swift_bic,
         | 
| 12 | 
            +
                            :payment_id
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              alias_method :type, :kind
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def initialize(attributes = {}, parent = nil)
         | 
| 17 | 
            +
                if attributes
         | 
| 18 | 
            +
                  self.paid_at      = DateTime.parse(attributes.delete('paid_at')) unless attributes['paid_at'].blank?
         | 
| 19 | 
            +
                  self.refunded_at  = DateTime.parse(attributes.delete('refunded_at')) unless attributes['refunded_at'].blank?
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                super
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def paid?
         | 
| 26 | 
            +
                self.paid_at.is_a?(DateTime)
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def refunded?
         | 
| 30 | 
            +
                self.refunded_at.is_a?(DateTime)
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            class Cornerstore::Price < Cornerstore::Model::Base
         | 
| 2 | 
            +
              include Comparable
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              attr_accessor :gross,
         | 
| 5 | 
            +
                            :net,
         | 
| 6 | 
            +
                            :tax,
         | 
| 7 | 
            +
                            :tax_rate,
         | 
| 8 | 
            +
                            :currency,
         | 
| 9 | 
            +
                            :amount
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              validates :gross, numericality: { greater_than_or_equal_to: 0 }, if: Proc.new { |p| not p.amount }
         | 
| 12 | 
            +
              validates :amount, numericality: { greater_than_or_equal_to: 0 }, if: Proc.new { |p| not p.gross }
         | 
| 13 | 
            +
              validates :net, numericality: { greater_than_or_equal_to: 0, allow_nil: true }
         | 
| 14 | 
            +
              validates :tax_rate, numericality: { greater_than_or_equal_to: 0, allow_nil: true }
         | 
| 15 | 
            +
              validates :currency, presence: true, inclusion: { in: %w( EUR USD ) }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def attributes
         | 
| 18 | 
            +
                if gross or tax_rate
         | 
| 19 | 
            +
                  {
         | 
| 20 | 
            +
                    gross: gross,
         | 
| 21 | 
            +
                    net: net,
         | 
| 22 | 
            +
                    tax_rate: tax_rate,
         | 
| 23 | 
            +
                    currency: currency
         | 
| 24 | 
            +
                  }
         | 
| 25 | 
            +
                else
         | 
| 26 | 
            +
                  {
         | 
| 27 | 
            +
                    amount: amount,
         | 
| 28 | 
            +
                    currency: currency
         | 
| 29 | 
            +
                  }
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def <=> (other_object)
         | 
| 34 | 
            +
                case other_object
         | 
| 35 | 
            +
                when Integer, Float, Fixnum
         | 
| 36 | 
            +
                  self.amount <=> other_object
         | 
| 37 | 
            +
                when Cornerstore::Price
         | 
| 38 | 
            +
                  self.amount <=> other_object.amount
         | 
| 39 | 
            +
                else
         | 
| 40 | 
            +
                  raise ArgumentError, 'can only compare Integer, Float, Fixnum or Price objects with Price'
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              def +(other_object)
         | 
| 45 | 
            +
                return self if not other_object or other_object == 0
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                if gross
         | 
| 48 | 
            +
                  Cornerstore::Price.new({
         | 
| 49 | 
            +
                    gross:    gross + other_object.gross,
         | 
| 50 | 
            +
                    net:      net + other_object.net,
         | 
| 51 | 
            +
                    currency: currency,
         | 
| 52 | 
            +
                    tax_rate: other_object.tax_rate == tax_rate ? tax_rate : nil
         | 
| 53 | 
            +
                  })
         | 
| 54 | 
            +
                else
         | 
| 55 | 
            +
                  Cornerstore::Price.new({
         | 
| 56 | 
            +
                    amount:   amount + other_object.amount,
         | 
| 57 | 
            +
                    currency: currency
         | 
| 58 | 
            +
                  })
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              def to_f
         | 
| 64 | 
            +
                @amount || @gross
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              def amount
         | 
| 68 | 
            +
                @amount || @gross
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              def currency_symbol
         | 
| 72 | 
            +
                {
         | 
| 73 | 
            +
                  'USD' => '$',
         | 
| 74 | 
            +
                  'EUR' => '€',
         | 
| 75 | 
            +
                }[currency]
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
            end
         | 
| @@ -0,0 +1,72 @@ | |
| 1 | 
            +
            class Cornerstore::Product < Cornerstore::Model::Base
         | 
| 2 | 
            +
              attr_accessor :name,
         | 
| 3 | 
            +
                            :description,
         | 
| 4 | 
            +
                            :manufacturer,
         | 
| 5 | 
            +
                            :enabled,
         | 
| 6 | 
            +
                            :variants,
         | 
| 7 | 
            +
                            :images,
         | 
| 8 | 
            +
                            :properties
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def initialize(attributes = {}, parent = nil)
         | 
| 11 | 
            +
                self.images     = Cornerstore::Image::Resource.new(self, attributes.delete('images') || [], 'images')
         | 
| 12 | 
            +
                self.variants   = Cornerstore::Variant::Resource.new(self, attributes.delete('variants') || [], 'variants')
         | 
| 13 | 
            +
                self.properties = Cornerstore::Property::Resource.new(self, attributes.delete('properties') || [], 'properties')
         | 
| 14 | 
            +
                super
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def attributes
         | 
| 18 | 
            +
                {
         | 
| 19 | 
            +
                  name: name,
         | 
| 20 | 
            +
                  description: description,
         | 
| 21 | 
            +
                  manufacturer: manufacturer,
         | 
| 22 | 
            +
                  enabled: enabled
         | 
| 23 | 
            +
                }
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              def price
         | 
| 27 | 
            +
                variants.collect { |v| v.price }.sort.first
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def order_number
         | 
| 31 | 
            +
                variants.many? ? :many : variants.first.order_number
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def sold_out?
         | 
| 35 | 
            +
                variants.all? { |v| v.sold_out? }
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              def offer?
         | 
| 39 | 
            +
                variants.any? { |v| v.offer? }
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              def new?
         | 
| 43 | 
            +
                self.created_at >= 2.weeks.ago
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              def cover_image
         | 
| 47 | 
            +
                @cover_image ||= self.images.to_a.find {|i| i.cover == true }
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              class Resource < Cornerstore::Resource::Base
         | 
| 51 | 
            +
                include Cornerstore::Resource::Remote
         | 
| 52 | 
            +
                include Cornerstore::Resource::Filter
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def enabled
         | 
| 55 | 
            +
                  self.clone.set_filter(:enabled, true)
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def by_collection(collection_id)
         | 
| 59 | 
            +
                  self.clone.tap{|r| r.parent = Cornerstore::Collection.find(collection_id)}
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
                alias find_by_collection by_collection
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def order(key)
         | 
| 64 | 
            +
                  first = %w(name popularity created_at price)
         | 
| 65 | 
            +
                  second = %w(desc asc)
         | 
| 66 | 
            +
                  pattern = /\A(#{first.join('|')})( (#{second.join('|')}))?\z/i
         | 
| 67 | 
            +
                  raise "order key must be one of: #{first.join(', ')} [#{second.join(', ')}]" unless key.match pattern
         | 
| 68 | 
            +
                  super
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
                alias order_by order
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
            end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            class Cornerstore::Property < Cornerstore::Model::Base
         | 
| 2 | 
            +
              include Cornerstore::Model::Writable
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              attr_accessor :key,
         | 
| 5 | 
            +
                            :value
         | 
| 6 | 
            +
              
         | 
| 7 | 
            +
              def attributes
         | 
| 8 | 
            +
                {
         | 
| 9 | 
            +
                  key: key,
         | 
| 10 | 
            +
                  value: value
         | 
| 11 | 
            +
                }
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
              
         | 
| 14 | 
            +
              # TODO: Rewrite #url on base model to automatically detect parent/url depth.
         | 
| 15 | 
            +
              def url
         | 
| 16 | 
            +
                super(2)
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
              
         | 
| 19 | 
            +
              def initialize(attributes = {}, parent = nil)
         | 
| 20 | 
            +
                @key = attributes.delete(:key)
         | 
| 21 | 
            +
                @value = attributes.delete(:value)
         | 
| 22 | 
            +
                    
         | 
| 23 | 
            +
                super
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
              
         | 
| 26 | 
            +
              class Resource < Cornerstore::Resource::Base
         | 
| 27 | 
            +
                include Cornerstore::Resource::Remote
         | 
| 28 | 
            +
                include Cornerstore::Resource::Writable
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            class Cornerstore::Search
         | 
| 2 | 
            +
              extend ActiveModel::Naming
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              attr_accessor :keywords, :products, :scores
         | 
| 5 | 
            +
              alias :results :products
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def initialize(keywords = nil)
         | 
| 8 | 
            +
                @keywords, @scores, @products = keywords, Array.new, Cornerstore::Product::Resource.new
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              def results?
         | 
| 12 | 
            +
                !@products.empty?
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def to_key
         | 
| 16 | 
            +
                ['']
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def persisted?
         | 
| 20 | 
            +
                false
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              def run
         | 
| 24 | 
            +
                return false unless @keywords
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                RestClient.get("#{Cornerstore.root_url}/products/search?keywords=#{URI::encode(@keywords)}", Cornerstore.headers) do |response, request, result, &block|
         | 
| 27 | 
            +
                  if response.code == 200
         | 
| 28 | 
            +
                    data = ActiveSupport::JSON.decode(response)
         | 
| 29 | 
            +
                    @scores   = data['scores']
         | 
| 30 | 
            +
                    @products = Cornerstore::Product::Resource.new(nil, data['products'])
         | 
| 31 | 
            +
                    return true
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            class Cornerstore::Shipment < Cornerstore::Model::Base
         | 
| 2 | 
            +
              attr_accessor :carrier,
         | 
| 3 | 
            +
                :tracking_number,
         | 
| 4 | 
            +
                :created_at,
         | 
| 5 | 
            +
                :line_item_ids
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              alias_method :shipped_at, :created_at
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              def initialize(attributes = {}, parent=nil)
         | 
| 10 | 
            +
                self.carrier  = Cornerstore::Carrier.new(attributes.delete('carrier'))
         | 
| 11 | 
            +
                self.line_item_ids = attributes.delete('shipped_items')
         | 
| 12 | 
            +
                super
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def line_items
         | 
| 16 | 
            +
                return [] unless self.parent
         | 
| 17 | 
            +
                self.parent.line_items.select { |li| self.line_item_ids.include?(li.id) }
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
              alias_method :shipped_items, :line_items
         | 
| 20 | 
            +
             | 
| 21 | 
            +
             | 
| 22 | 
            +
              class Resource < Cornerstore::Resource::Base
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            class Cornerstore::Variant < Cornerstore::Model::Base
         | 
| 2 | 
            +
              attr_accessor :order_number,
         | 
| 3 | 
            +
                            :price,
         | 
| 4 | 
            +
                            :qty_in_stock,
         | 
| 5 | 
            +
                            :qty_reserved,
         | 
| 6 | 
            +
                            :offer,
         | 
| 7 | 
            +
                            :oversell,
         | 
| 8 | 
            +
                            :weight,
         | 
| 9 | 
            +
                            :unit,
         | 
| 10 | 
            +
                            :properties,
         | 
| 11 | 
            +
                            :differentiating_properties
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              alias product parent
         | 
| 14 | 
            +
              alias offer? offer
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def attributes
         | 
| 17 | 
            +
                {
         | 
| 18 | 
            +
                  order_number: order_number,
         | 
| 19 | 
            +
                  price: price,
         | 
| 20 | 
            +
                  qty_in_stock: qty_in_stock,
         | 
| 21 | 
            +
                  qty_reserved: qty_reserved,
         | 
| 22 | 
            +
                  offer: offer,
         | 
| 23 | 
            +
                  oversell: oversell,
         | 
| 24 | 
            +
                  weight: weight,
         | 
| 25 | 
            +
                  unit: unit
         | 
| 26 | 
            +
                }
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def initialize(attributes = {}, parent = nil)
         | 
| 30 | 
            +
                self.price = Cornerstore::Price.new(attributes.delete('price'))
         | 
| 31 | 
            +
                self.properties = Cornerstore::Property::Resource.new(self, attributes.delete('properties') || [])
         | 
| 32 | 
            +
                super
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              def qty_available
         | 
| 36 | 
            +
                if self.qty_in_stock and not self.oversell
         | 
| 37 | 
            +
                  self.qty_in_stock - self.qty_reserved
         | 
| 38 | 
            +
                else
         | 
| 39 | 
            +
                  Float::INFINITY
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              def sold_out?
         | 
| 44 | 
            +
                self.qty_available == 0
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              class Resource < Cornerstore::Resource::Base
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,117 @@ | |
| 1 | 
            +
            module Cornerstore
         | 
| 2 | 
            +
              module Model
         | 
| 3 | 
            +
                class Base
         | 
| 4 | 
            +
                  include ActiveModel::Validations
         | 
| 5 | 
            +
                  include ActiveModel::Serializers::JSON
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  attr_accessor :_id
         | 
| 8 | 
            +
                  attr_accessor :_slugs
         | 
| 9 | 
            +
                  attr_accessor :parent
         | 
| 10 | 
            +
                  attr_accessor :created_at
         | 
| 11 | 
            +
                  attr_accessor :updated_at
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  alias id _id
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def initialize(attributes = {}, parent = nil)
         | 
| 16 | 
            +
                    self.attributes = attributes
         | 
| 17 | 
            +
                    self.parent = parent
         | 
| 18 | 
            +
                    yield self if block_given?
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def to_param
         | 
| 22 | 
            +
                    if _slugs and !_slugs.empty?
         | 
| 23 | 
            +
                      _slugs.first
         | 
| 24 | 
            +
                    else
         | 
| 25 | 
            +
                      _id
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def ==(other)
         | 
| 30 | 
            +
                    other.id == self.id
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  alias eql? ==
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def inspect
         | 
| 36 | 
            +
                    {class: self.class.name, id: id}.merge!(attributes).to_s
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def attributes
         | 
| 40 | 
            +
                    {}
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def attributes=(attributes)
         | 
| 44 | 
            +
                    attributes ||= {}
         | 
| 45 | 
            +
                    attributes.each_pair do |name, value|
         | 
| 46 | 
            +
                      send("#{name}=", value) if respond_to?("#{name}=")
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  def url(depth = 1)
         | 
| 51 | 
            +
                    root = (@parent && depth > 0) ? @parent.url(depth-1) : Cornerstore.root_url
         | 
| 52 | 
            +
                    "#{root}/#{self.class.name.split('::').last.underscore.pluralize}/#{id}"
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def self.method_missing(method, *args, &block)
         | 
| 56 | 
            +
                    if (self.const_defined?("Resource") and self.const_get("Resource").method_defined?(method))
         | 
| 57 | 
            +
                      self.const_get("Resource").new.send(method, *args, &block)
         | 
| 58 | 
            +
                    else
         | 
| 59 | 
            +
                      super
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def method_missing(method, *args, &block)
         | 
| 64 | 
            +
                    if Writable.method_defined?(method)
         | 
| 65 | 
            +
                      raise "Sorry, this part of the Cornerstore-API is currently read-only"
         | 
| 66 | 
            +
                    else
         | 
| 67 | 
            +
                      super
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                module Writable
         | 
| 73 | 
            +
                  def new?
         | 
| 74 | 
            +
                    id.nil?
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  alias new_record? new?
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  def to_key
         | 
| 80 | 
            +
                    new? ? [id] : nil
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  def save
         | 
| 84 | 
            +
                    return false unless valid?
         | 
| 85 | 
            +
                    wrapped_attributes = {self.class.name.split('::').last.underscore => self.attributes}
         | 
| 86 | 
            +
                    if new?
         | 
| 87 | 
            +
                      response = RestClient.post(url, wrapped_attributes, Cornerstore.headers){|response| response}
         | 
| 88 | 
            +
                      self.attributes = ActiveSupport::JSON.decode(response)
         | 
| 89 | 
            +
                    else
         | 
| 90 | 
            +
                      response = RestClient.patch(url, wrapped_attributes, Cornerstore.headers){|response| response}
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    if response.success?
         | 
| 94 | 
            +
                      return true
         | 
| 95 | 
            +
                    else
         | 
| 96 | 
            +
                      if data = ActiveSupport::JSON.decode(response) and data.has_key?('errors')
         | 
| 97 | 
            +
                        data['errors'].each_pair do |key, messages|
         | 
| 98 | 
            +
                          messages.map { |message| self.errors.add(key, message) }
         | 
| 99 | 
            +
                        end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                        return false
         | 
| 102 | 
            +
                      else
         | 
| 103 | 
            +
                        return false
         | 
| 104 | 
            +
                      end
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  def destroy
         | 
| 109 | 
            +
                    RestClient.delete(url, Cornerstore.headers).success?
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  def self.create(attributes = {}, &block)
         | 
| 113 | 
            +
                    self.new(attributes, &block).tap{|obj| obj.save}
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
              end
         | 
| 117 | 
            +
            end
         |