radiant-shop-extension 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +26 -0
  3. data/Gemfile.lock +75 -0
  4. data/HISTORY.md +77 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +26 -0
  7. data/Rakefile +141 -0
  8. data/VERSION +1 -0
  9. data/app/.DS_Store +0 -0
  10. data/app/controllers/admin/shop/categories_controller.rb +206 -0
  11. data/app/controllers/admin/shop/customers_controller.rb +137 -0
  12. data/app/controllers/admin/shop/orders_controller.rb +171 -0
  13. data/app/controllers/admin/shop/products/images_controller.rb +144 -0
  14. data/app/controllers/admin/shop/products_controller.rb +217 -0
  15. data/app/controllers/admin/shops_controller.rb +9 -0
  16. data/app/controllers/shop/categories_controller.rb +49 -0
  17. data/app/controllers/shop/line_items_controller.rb +165 -0
  18. data/app/controllers/shop/orders_controller.rb +16 -0
  19. data/app/controllers/shop/products_controller.rb +48 -0
  20. data/app/models/form_checkout.rb +245 -0
  21. data/app/models/shop_address.rb +13 -0
  22. data/app/models/shop_addressable.rb +11 -0
  23. data/app/models/shop_category.rb +85 -0
  24. data/app/models/shop_category_page.rb +7 -0
  25. data/app/models/shop_customer.rb +27 -0
  26. data/app/models/shop_line_item.rb +32 -0
  27. data/app/models/shop_order.rb +108 -0
  28. data/app/models/shop_payment.rb +6 -0
  29. data/app/models/shop_payment_method.rb +5 -0
  30. data/app/models/shop_product.rb +87 -0
  31. data/app/models/shop_product_attachment.rb +30 -0
  32. data/app/models/shop_product_page.rb +7 -0
  33. data/app/views/.DS_Store +0 -0
  34. data/app/views/admin/.DS_Store +0 -0
  35. data/app/views/admin/pages/_shop_category.html.haml +4 -0
  36. data/app/views/admin/pages/_shop_product.html.haml +4 -0
  37. data/app/views/admin/shop/categories/edit.html.haml +12 -0
  38. data/app/views/admin/shop/categories/edit/_fields.html.haml +28 -0
  39. data/app/views/admin/shop/categories/edit/_head.html.haml +2 -0
  40. data/app/views/admin/shop/categories/edit/_meta.html.haml +7 -0
  41. data/app/views/admin/shop/categories/edit/_part.html.haml +3 -0
  42. data/app/views/admin/shop/categories/edit/_popup.html.haml +3 -0
  43. data/app/views/admin/shop/categories/edit/meta/_handle.html.haml +5 -0
  44. data/app/views/admin/shop/categories/edit/meta/_layouts.html.haml +10 -0
  45. data/app/views/admin/shop/categories/edit/parts/_description.html.haml +1 -0
  46. data/app/views/admin/shop/categories/index/_category.html.haml +16 -0
  47. data/app/views/admin/shop/categories/new.html.haml +10 -0
  48. data/app/views/admin/shop/categories/remove.html.haml +12 -0
  49. data/app/views/admin/shop/customers/index.html.haml +36 -0
  50. data/app/views/admin/shop/orders/index.html.haml +33 -0
  51. data/app/views/admin/shop/products/edit.html.haml +14 -0
  52. data/app/views/admin/shop/products/edit/_fields.html.haml +38 -0
  53. data/app/views/admin/shop/products/edit/_head.html.haml +2 -0
  54. data/app/views/admin/shop/products/edit/_image.html.haml +12 -0
  55. data/app/views/admin/shop/products/edit/_meta.html.haml +8 -0
  56. data/app/views/admin/shop/products/edit/_part.html.haml +3 -0
  57. data/app/views/admin/shop/products/edit/_popup.html.haml +3 -0
  58. data/app/views/admin/shop/products/edit/buttons/_browse_images.html.haml +1 -0
  59. data/app/views/admin/shop/products/edit/buttons/_new_image.html.haml +1 -0
  60. data/app/views/admin/shop/products/edit/meta/_category.html.haml +5 -0
  61. data/app/views/admin/shop/products/edit/meta/_sku.html.haml +5 -0
  62. data/app/views/admin/shop/products/edit/parts/_description.html.haml +1 -0
  63. data/app/views/admin/shop/products/edit/parts/_images.html.haml +5 -0
  64. data/app/views/admin/shop/products/edit/popups/_browse_images.html.haml +6 -0
  65. data/app/views/admin/shop/products/edit/popups/_new_image.html.haml +18 -0
  66. data/app/views/admin/shop/products/index.html.haml +10 -0
  67. data/app/views/admin/shop/products/index/_bottom.html.haml +5 -0
  68. data/app/views/admin/shop/products/index/_product.html.haml +13 -0
  69. data/app/views/admin/shop/products/new.html.haml +10 -0
  70. data/app/views/admin/shop/products/remove.html.haml +12 -0
  71. data/app/views/shop/categories/show.html.haml +1 -0
  72. data/app/views/shop/orders/show.html.haml +1 -0
  73. data/app/views/shop/products/index.html.haml +1 -0
  74. data/app/views/shop/products/show.html.haml +1 -0
  75. data/config/locales/en.yml +50 -0
  76. data/config/routes.rb +38 -0
  77. data/config/shop_cart.yml +16 -0
  78. data/cucumber.yml +1 -0
  79. data/db/migrate/20100311053701_initial.rb +153 -0
  80. data/db/migrate/20100520033059_create_layouts.rb +119 -0
  81. data/db/migrate/20100903122123_create_forms.rb +44 -0
  82. data/db/migrate/20100908063639_create_snippets.rb +22 -0
  83. data/features/support/env.rb +16 -0
  84. data/features/support/paths.rb +14 -0
  85. data/lib/shop/controllers/application_controller.rb +39 -0
  86. data/lib/shop/controllers/site_controller.rb +12 -0
  87. data/lib/shop/interface/products.rb +49 -0
  88. data/lib/shop/models/image.rb +14 -0
  89. data/lib/shop/models/page.rb +14 -0
  90. data/lib/shop/tags/address.rb +40 -0
  91. data/lib/shop/tags/cart.rb +49 -0
  92. data/lib/shop/tags/category.rb +83 -0
  93. data/lib/shop/tags/core.rb +12 -0
  94. data/lib/shop/tags/helpers.rb +142 -0
  95. data/lib/shop/tags/item.rb +109 -0
  96. data/lib/shop/tags/product.rb +109 -0
  97. data/lib/shop/tags/responses.rb +34 -0
  98. data/lib/tasks/shop_extension_tasks.rake +54 -0
  99. data/mockups/balsamiq/products-retro_fun_tshirt-more.bmml +0 -0
  100. data/mockups/balsamiq/products-retro_fun_tshirt.bmml +0 -0
  101. data/public/images/admin/extensions/shop/products/sort.png +0 -0
  102. data/public/javascripts/admin/extensions/shop/products/edit.js +115 -0
  103. data/public/javascripts/admin/extensions/shop/products/index.js +65 -0
  104. data/public/stylesheets/sass/admin/extensions/shop/edit.sass +86 -0
  105. data/public/stylesheets/sass/admin/extensions/shop/products/edit.sass +158 -0
  106. data/public/stylesheets/sass/admin/extensions/shop/products/index.sass +129 -0
  107. data/radiant-shop-extension.gemspec +245 -0
  108. data/shop_extension.rb +62 -0
  109. data/spec/controllers/admin/shop/categories_controller_spec.rb +443 -0
  110. data/spec/controllers/admin/shop/products/images_controller_spec.rb +477 -0
  111. data/spec/controllers/admin/shop/products_controller_spec.rb +394 -0
  112. data/spec/controllers/admin/shops_controller_spec.rb +19 -0
  113. data/spec/controllers/shop/categories_controller_spec.rb +42 -0
  114. data/spec/controllers/shop/line_items_controller_spec.rb +256 -0
  115. data/spec/controllers/shop/orders_controller_specs.rb +37 -0
  116. data/spec/controllers/shop/products_controller_spec.rb +51 -0
  117. data/spec/datasets/forms.rb +153 -0
  118. data/spec/datasets/images.rb +13 -0
  119. data/spec/datasets/shop_addresses.rb +27 -0
  120. data/spec/datasets/shop_categories.rb +13 -0
  121. data/spec/datasets/shop_line_items.rb +9 -0
  122. data/spec/datasets/shop_orders.rb +21 -0
  123. data/spec/datasets/shop_products.rb +34 -0
  124. data/spec/helpers/nested_tag_helper.rb +33 -0
  125. data/spec/lib/shop/models/image_spec.rb +28 -0
  126. data/spec/lib/shop/models/page_spec.rb +38 -0
  127. data/spec/lib/shop/tags/address_spec.rb +196 -0
  128. data/spec/lib/shop/tags/cart_spec.rb +169 -0
  129. data/spec/lib/shop/tags/category_spec.rb +201 -0
  130. data/spec/lib/shop/tags/core_spec.rb +16 -0
  131. data/spec/lib/shop/tags/helpers_spec.rb +381 -0
  132. data/spec/lib/shop/tags/item_spec.rb +313 -0
  133. data/spec/lib/shop/tags/product_spec.rb +334 -0
  134. data/spec/matchers/comparison.rb +72 -0
  135. data/spec/matchers/render_matcher.rb +33 -0
  136. data/spec/models/form_checkout_spec.rb +376 -0
  137. data/spec/models/shop_category_page_spec.rb +10 -0
  138. data/spec/models/shop_category_spec.rb +58 -0
  139. data/spec/models/shop_line_item_spec.rb +42 -0
  140. data/spec/models/shop_order_spec.rb +228 -0
  141. data/spec/models/shop_product_attachment_spec.rb +72 -0
  142. data/spec/models/shop_product_page_spec.rb +10 -0
  143. data/spec/models/shop_product_spec.rb +135 -0
  144. data/spec/spec.opts +6 -0
  145. data/spec/spec_helper.rb +22 -0
  146. data/vendor/plugins/acts_as_list/README +23 -0
  147. data/vendor/plugins/acts_as_list/init.rb +3 -0
  148. data/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
  149. data/vendor/plugins/acts_as_list/test/list_test.rb +332 -0
  150. data/vendor/plugins/json_fields/.gitignore +3 -0
  151. data/vendor/plugins/json_fields/MIT-LICENSE +20 -0
  152. data/vendor/plugins/json_fields/README.rdoc +65 -0
  153. data/vendor/plugins/json_fields/Rakefile +55 -0
  154. data/vendor/plugins/json_fields/init.rb +2 -0
  155. data/vendor/plugins/json_fields/lib/json_fields.rb +99 -0
  156. data/vendor/plugins/json_fields/spec/lib/json_fields_spec.rb +124 -0
  157. data/vendor/plugins/json_fields/spec/spec.opts +6 -0
  158. data/vendor/plugins/json_fields/spec/spec_helper.rb +29 -0
  159. data/vendor/plugins/json_fields/spec/test_models.rb +12 -0
  160. metadata +324 -0
@@ -0,0 +1,48 @@
1
+ class Shop::ProductsController < ApplicationController
2
+
3
+ skip_before_filter :verify_authenticity_token
4
+ no_login_required
5
+
6
+ radiant_layout Radiant::Config['shop.product_layout']
7
+
8
+ rescue_from ActiveRecord::RecordNotFound do |exception|
9
+ render :template => 'site/not_found', :status => :unprocessable_entity
10
+ end
11
+
12
+ # GET /shop/search/:query
13
+ # GET /shop/search/:query.js
14
+ # GET /shop/search/:query.json AJAX and HTML
15
+ #----------------------------------------------------------------------------
16
+ def index
17
+ @shop_products = ShopProduct.search(params[:query])
18
+ @radiant_layout = Radiant::Config['shop.category_layout']
19
+
20
+ respond_to do |format|
21
+ format.html { render }
22
+ format.js { render :partial => '/shop/products/index/product', :collection => @shop_products }
23
+ format.json { render :json => @shop_products.to_json(ShopProduct.params) }
24
+ end
25
+ end
26
+
27
+ # GET /shop/:category_handle/:handle
28
+ # GET /shop/:category_handle/:handle.js
29
+ # GET /shop/:category_handle/:handle.json AJAX and HTML
30
+ #----------------------------------------------------------------------------
31
+ def show
32
+ if @shop_product = ShopProduct.find(:first, :conditions => { :sku => params[:sku] })
33
+ @shop_category = @shop_product.category unless @shop_product.nil?
34
+
35
+ @radiant_layout = @shop_product.layout.name
36
+ @title = @shop_product.name
37
+
38
+ respond_to do |format|
39
+ format.html { render }
40
+ format.js { render :partial => '/shop/products/index/product', :locals => { :product => @shop_product } }
41
+ format.json { render :json => @shop_product.to_json(ShopProduct.params) }
42
+ end
43
+ else
44
+ raise ActiveRecord::RecordNotFound
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,245 @@
1
+ class FormCheckout
2
+ include Forms::Models::Extension
3
+
4
+ def create
5
+ @result = {
6
+ :billing => false,
7
+ :shipping => false,
8
+ :gateway => false,
9
+ :card => false,
10
+ :payment => false
11
+ }
12
+
13
+ @success= true
14
+ @order = ShopOrder.find(@page.request[:session][:shop_order])
15
+
16
+ if @config[:address].present?
17
+ # If the form was configured for address
18
+ build_addresses
19
+ end
20
+
21
+ if @config[:gateway].present? and card
22
+ # If the form was configured for gateway
23
+ build_gateway
24
+ build_card
25
+
26
+ purchase
27
+ end
28
+
29
+ if @result[:payment]
30
+ @order.update_attribute(:status, 'paid')
31
+ end
32
+
33
+ @result
34
+ end
35
+
36
+ private
37
+
38
+ # Assigns a shipping and billing address to the @order
39
+ def build_addresses
40
+ if billing.present?
41
+ # We're going to create a billing object
42
+ build_billing_address
43
+
44
+ unless shipping.present? and @billing.valid?
45
+ # We're going to assign shipping to billing
46
+ @shipping = @billing
47
+ @order.update_attribute(:shipping_id, @shipping.id)
48
+ end
49
+ end
50
+
51
+ if shipping.present?
52
+ build_shipping_address
53
+ end
54
+
55
+ @result[:billing] = @order.billing.present? ? @order.billing : false
56
+ @result[:shipping] = @order.shipping.present? ? @order.shipping : false
57
+
58
+ unless @order.billing.present? and @order.shipping.present?
59
+ @form.redirect_to = :back
60
+ end
61
+ end
62
+
63
+ def build_billing_address
64
+ # Billing Address
65
+ if billing['id'].present?
66
+ # Use an existing Address and update its values
67
+ @billing = ShopAddress.find(billing['id'])
68
+ @billing.update_attributes(billing)
69
+ @order.update_attribute(:billing_id, @billing.id)
70
+
71
+ elsif @order.billing.present?
72
+ # Use the current billing and update its values
73
+ @billing = @order.billing
74
+ @billing.update_attributes(billing)
75
+
76
+ else
77
+ # Create a new address with these attributes
78
+ @billing = ShopAddress.new(billing)
79
+ if @billing.save
80
+ @order.update_attribute(:billing_id, @billing.id)
81
+ end
82
+
83
+ end
84
+ end
85
+
86
+ def build_shipping_address
87
+ # Shipping Address
88
+ if shipping['id'].present?
89
+ # Use an existing Address and update its values
90
+ @shipping = ShopAddress.find(shipping['id'])
91
+ if @shipping == @billing and shipping == billing
92
+ # We have exactly the same shipping and billing data
93
+ @shipping = @billing
94
+ @order.update_attribute(:shipping_id, @billing.id)
95
+ elsif (shipping.reject!{|k,v|k=='id'}).values.all?(&:blank?)
96
+ # We have just passed the id and not the data
97
+ @order.update_attribute(:shipping_id, @shipping.id)
98
+ elsif @shipping == @billing and shipping != billing
99
+ # We have conflicting data so create a new address
100
+ # the id is rejected so we'll get a new address
101
+ @order.update_attributes!({ :shipping_attributes => shipping })
102
+ @shipping = @order.shipping
103
+ end
104
+
105
+ elsif @order.shipping.present?
106
+ # Use the current shipping and update its values
107
+ @shipping = @order.shipping
108
+ @shipping.update_attributes(shipping)
109
+
110
+ elsif shipping.values.all?(&:blank?) or shipping == billing
111
+ # We haven't set a shipping, or we have copied billing, so use billing
112
+ @shipping = @billing
113
+ @order.update_attribute(:shipping_id, @billing.id)
114
+
115
+ else
116
+ # Create a new address with these attributes
117
+ @order.update_attributes!({ :shipping_attributes => shipping })
118
+ @shipping = @order.shipping
119
+ end
120
+ end
121
+
122
+ # Creates a gateway instance variable based off the form configuration
123
+ def build_gateway
124
+ ActiveMerchant::Billing::Base.mode = :test if testing
125
+
126
+ begin
127
+ @gateway = ActiveMerchant::Billing.const_get("#{gateway}Gateway").new(gateway_credentials)
128
+ @result[:gateway] = true
129
+ end
130
+ end
131
+
132
+ # Builds an ActiveMerchant card using the submitted card information
133
+ def build_card
134
+ if card.present?
135
+ @card = ActiveMerchant::Billing::CreditCard.new({
136
+ :number => card_number,
137
+ :month => card_month,
138
+ :year => card_year,
139
+ :first_name => card_first_name,
140
+ :last_name => card_last_name,
141
+ :verification_value => card_verification,
142
+ :type => card_type
143
+ })
144
+
145
+ @result[:card] = {
146
+ :valid => @card.valid?
147
+ }
148
+ end
149
+ end
150
+
151
+ def purchase
152
+ result = @gateway.purchase(amount, @card, options)
153
+
154
+ @result[:payment] = {
155
+ :success => result.success?,
156
+ :message => result.message
157
+ }
158
+
159
+ unless result.success?
160
+ @form.redirect_to = :back
161
+ end
162
+ end
163
+
164
+ def success?
165
+ @success
166
+ end
167
+
168
+ def testing
169
+ @config[:test].present?
170
+ end
171
+
172
+ def billing
173
+ billing = @data['billing'] # Array of billing attributes
174
+ end
175
+
176
+ def shipping
177
+ shipping = @data['shipping'] # Array of shipping attributes
178
+ end
179
+
180
+ def gateway
181
+ @config[:gateway][:name]
182
+ end
183
+
184
+ def gateway_credentials
185
+ @config[:gateway][:credentials]
186
+ end
187
+
188
+ def card
189
+ @data['card']
190
+ end
191
+
192
+ def card_number
193
+ card['number'].to_s.gsub('-', '').to_i
194
+ end
195
+
196
+ def card_month
197
+ card['month'].to_i
198
+ end
199
+
200
+ def card_year
201
+ card['year'].to_i
202
+ end
203
+
204
+ def card_type
205
+ card['type']
206
+ end
207
+
208
+ def card_verification
209
+ card['verification']
210
+ end
211
+
212
+ def card_names
213
+ return @card_names if @card_names.present?
214
+ @card_names = @data['card']['name'].split(' ')
215
+ end
216
+
217
+ def card_first_name
218
+ card_names[0, card_names.length - 1].join(' ') if card_names.present?
219
+ end
220
+
221
+ def card_last_name
222
+ card_names[-1] if card_names.present?
223
+ end
224
+
225
+ def amount
226
+ result = 0
227
+
228
+ if testing
229
+ result = 1000
230
+ else
231
+ result = (@order.price * 100)
232
+ end
233
+
234
+ result
235
+ end
236
+
237
+ def options
238
+ @config[:options] ||= {}
239
+
240
+ @config[:options][:order_id] = @order.id
241
+
242
+ @config[:options]
243
+ end
244
+
245
+ end
@@ -0,0 +1,13 @@
1
+ class ShopAddress < ActiveRecord::Base
2
+
3
+ has_many :shipping_orders, :class_name => 'ShopOrder', :foreign_key => :shipping_id
4
+ has_many :billing_orders, :class_name => 'ShopOrder', :foreign_key => :billing_id
5
+
6
+ validates_presence_of :name
7
+ validates_presence_of :street
8
+ validates_presence_of :city
9
+ validates_presence_of :postcode
10
+ validates_presence_of :state
11
+ validates_presence_of :country
12
+
13
+ end
@@ -0,0 +1,11 @@
1
+ class ShopAddressable < ActiveRecord::Base
2
+
3
+ belongs_to :address, :polymorphic => true
4
+ belongs_to :addresser, :polymorphic => true
5
+
6
+ validates_presence_of :address_id
7
+ validates_presence_of :address_type
8
+ validates_presence_of :addresser_id
9
+ validates_presence_of :addresser_type
10
+
11
+ end
@@ -0,0 +1,85 @@
1
+ class ShopCategory < ActiveRecord::Base
2
+
3
+ default_scope :order => 'position ASC'
4
+
5
+ belongs_to :created_by, :class_name => 'User'
6
+ belongs_to :updated_by, :class_name => 'User'
7
+ belongs_to :layout
8
+ belongs_to :product_layout, :class_name => 'Layout'
9
+
10
+ has_many :products, :class_name => 'ShopProduct', :dependent => :destroy
11
+
12
+ before_validation :set_handle, :filter_handle, :set_layouts
13
+
14
+ validates_presence_of :name, :handle
15
+
16
+ validates_uniqueness_of :name, :handle
17
+
18
+ acts_as_list
19
+
20
+ def custom=(values)
21
+ values.each do |key, value|
22
+ self.json_field_set(key, value)
23
+ end
24
+ end
25
+
26
+ def slug
27
+ "/#{self.slug_prefix}/#{self.handle}"
28
+ end
29
+
30
+ def slug_prefix
31
+ Radiant::Config['shop.url_prefix']
32
+ end
33
+
34
+ class << self
35
+
36
+ def search(search)
37
+ unless search.blank?
38
+ queries = []
39
+ queries << 'LOWER(title) LIKE (:term)'
40
+ queries << 'LOWER(handle) LIKE (:term)'
41
+ queries << 'LOWER(description) LIKE (:term)'
42
+
43
+ sql = queries.join(" OR ")
44
+ conditions = [sql, {:term => "%#{search.downcase}%" }]
45
+ else
46
+ conditions = []
47
+ end
48
+
49
+ all({ :conditions => conditions })
50
+ end
51
+
52
+ def attrs
53
+ [ :id, :handle, :description, :created_at, :updated_at ]
54
+ end
55
+
56
+ def params
57
+ {
58
+ :include => { :products => { :only => ShopProduct.attrs } },
59
+ :only => ShopCategory.attrs
60
+ }
61
+ end
62
+
63
+ end
64
+
65
+ private
66
+
67
+ def set_handle
68
+ unless self.name.nil?
69
+ self.handle = self.name if self.handle.nil? or self.handle.empty?
70
+ end
71
+ end
72
+
73
+ def filter_handle
74
+ unless self.name.nil?
75
+ self.handle = self.handle.downcase.gsub(/[^-a-z0-9~\s\.:;+=_]/, '').strip.gsub(/[\s\.:;=+~]+/, '_')
76
+ end
77
+ self.handle.downcase
78
+ end
79
+
80
+ def set_layouts
81
+ self.layout = Layout.find_by_name(Radiant::Config['shop.category_layout']) if self.layout.nil?
82
+ self.product_layout = Layout.find_by_name(Radiant::Config['shop.product_layout']) if self.product_layout.nil?
83
+ end
84
+
85
+ end
@@ -0,0 +1,7 @@
1
+ class ShopCategoryPage < Page
2
+
3
+ def cache?
4
+ return false
5
+ end
6
+
7
+ end
@@ -0,0 +1,27 @@
1
+ class ShopCustomer < User
2
+
3
+ default_scope where(:all, :conditions => { :access => 'shop' })
4
+
5
+ has_many :orders, :class_name => 'ShopOrder'
6
+ has_many :addressables, :class_name => 'ShopAddressable', :as => :addresser
7
+ has_many :billings, :through => :addressables, :source => :address, :source_type => 'ShopAddressBilling', :uniq => true
8
+ has_many :shippings, :through => :addressables, :source => :address, :source_type => 'ShopAddressShipping', :uniq => true
9
+
10
+ accepts_nested_attributes_for :orders, :allow_destroy => true
11
+
12
+ before_save :set_access
13
+
14
+ def first_name
15
+ self.name.split(' ')[0]
16
+ end
17
+
18
+ def last_name
19
+ self.name.split(' ')[1]
20
+ end
21
+
22
+ private
23
+ def set_access
24
+ self.access = 'shop'
25
+ end
26
+
27
+ end
@@ -0,0 +1,32 @@
1
+ class ShopLineItem < ActiveRecord::Base
2
+
3
+ belongs_to :order, :class_name => 'ShopOrder', :foreign_key => :shop_order_id
4
+ belongs_to :item, :polymorphic => true
5
+
6
+ validates_uniqueness_of :item_id, :scope => [ :shop_order_id, :item_type ]
7
+
8
+ before_validation :adjust_quantity
9
+
10
+ def price
11
+ item.price.to_f * self.quantity.to_f
12
+ end
13
+
14
+ def weight
15
+ item.weight.to_f * self.quantity.to_f
16
+ end
17
+
18
+ class << self
19
+
20
+ def params
21
+ [ :id, :quantity ]
22
+ end
23
+
24
+ end
25
+
26
+ private
27
+
28
+ def adjust_quantity
29
+ self.quantity = [1,self.quantity].max
30
+ end
31
+
32
+ end