spree_core 0.70.RC1 → 0.70.0.rc2
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.
- data/README.md +10 -4
- data/app/assets/javascripts/admin/admin.js.erb +1 -1
- data/app/assets/javascripts/admin/orders/edit_form.js +3 -1
- data/app/assets/stylesheets/admin/admin-form.css.erb +1 -3
- data/app/assets/stylesheets/admin/admin-tables.css.erb +1 -1
- data/app/assets/stylesheets/admin/admin.css.erb +2 -2
- data/app/assets/stylesheets/admin/spree_core.css +0 -1
- data/app/controllers/admin/line_items_controller.rb +3 -1
- data/app/controllers/checkout_controller.rb +6 -0
- data/app/helpers/spree/base_helper.rb +21 -10
- data/app/models/creditcard.rb +15 -1
- data/app/models/inventory_unit.rb +6 -10
- data/app/models/line_item.rb +17 -2
- data/app/models/order.rb +5 -3
- data/app/models/product.rb +33 -21
- data/app/models/taxonomy.rb +1 -1
- data/app/views/admin/mail_methods/_form.html.erb +3 -3
- data/app/views/admin/orders/_line_item.html.erb +3 -1
- data/app/views/admin/orders/index.html.erb +3 -3
- data/app/views/admin/payments/source_views/_gateway.html.erb +1 -1
- data/app/views/admin/products/_form.html.erb +3 -3
- data/app/views/admin/products/new.html.erb +3 -3
- data/app/views/admin/shared/_report_criteria.html.erb +3 -3
- data/app/views/admin/variants/_form.html.erb +3 -3
- data/app/views/admin/variants/index.html.erb +1 -1
- data/app/views/layouts/admin.html.erb +1 -1
- data/app/views/order_mailer/confirm_email.text.erb +1 -1
- data/app/views/orders/_line_item.html.erb +7 -2
- data/config/initializers/rails_3_1.rb +12 -0
- data/config/locales/en.yml +2 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +1 -0
- data/lib/generators/spree/site/site_generator.rb +7 -7
- data/lib/scopes/product.rb +6 -6
- data/lib/spree_base.rb +5 -6
- data/lib/spree_core/railtie.rb +1 -1
- data/lib/spree_core/version.rb +1 -1
- metadata +70 -68
- data/app/assets/stylesheets/admin/grids.css +0 -314
- data/config/initializers/disable_paperclip_log.rb +0 -2
data/README.md
CHANGED
@@ -1,15 +1,21 @@
|
|
1
|
+
Core
|
2
|
+
====
|
3
|
+
|
4
|
+
Core e-commerce functionality for the Spree project
|
5
|
+
|
6
|
+
|
1
7
|
Testing
|
2
|
-
|
8
|
+
-------
|
3
9
|
|
4
10
|
Create the test site
|
5
11
|
|
6
|
-
rake test_app
|
12
|
+
bundle exec rake test_app
|
7
13
|
|
8
14
|
Run the tests
|
9
15
|
|
10
|
-
rake spec
|
16
|
+
bundle exec rake spec
|
11
17
|
|
12
18
|
Run the coverage. After the rake task open coverage/index.html
|
13
19
|
|
14
|
-
rake rcov
|
20
|
+
bundle exec rake rcov
|
15
21
|
|
@@ -106,7 +106,7 @@ prep_product_autocomplete_data = function(data){
|
|
106
106
|
}
|
107
107
|
|
108
108
|
$.fn.product_autocomplete = function(){
|
109
|
-
$(this).autocomplete("/admin/products.json?authenticity_token=" +
|
109
|
+
$(this).autocomplete("/admin/products.json?authenticity_token=" + encodeURIComponent($('meta[name=csrf-token]').attr("content")), {
|
110
110
|
parse: prep_product_autocomplete_data,
|
111
111
|
formatItem: function(item) {
|
112
112
|
return format_product_autocomplete(item);
|
@@ -9,7 +9,9 @@ $.each($('td.qty input'), function(i, inpt){
|
|
9
9
|
type: "POST",
|
10
10
|
url: "/admin/orders/" + $('input#order_number').val() + "/line_items/" + $(id).val(),
|
11
11
|
data: ({_method: "put", "line_item[quantity]": $(this).val()}),
|
12
|
-
|
12
|
+
complete: function(resp){
|
13
|
+
$('#order-form-wrapper').html(resp.responseText);
|
14
|
+
}
|
13
15
|
});
|
14
16
|
|
15
17
|
}, 0,5);
|
@@ -53,9 +53,7 @@ fieldset#preferences textarea { height: 100px; }
|
|
53
53
|
|
54
54
|
|
55
55
|
.date-range-filter {width:220px;}
|
56
|
-
.date-range-filter input {width:
|
57
|
-
.date-range-filter .yui-u { width:50% }
|
58
|
-
|
56
|
+
.date-range-filter input {width:70px;}
|
59
57
|
|
60
58
|
/* Multi-column form layout
|
61
59
|
-------------------------------------------------------------- */
|
@@ -24,7 +24,7 @@ table.index th {
|
|
24
24
|
table.index tr.alt td {
|
25
25
|
background-color: #efefef; }
|
26
26
|
table.index.green th {
|
27
|
-
background: #cfefa7 url(<%= asset_path 'admin/grid_header_back_green.png' %>) top left repeat-x;
|
27
|
+
background: #cfefa7 url(<%= asset_path 'admin/bg/grid_header_back_green.png' %>) top left repeat-x;
|
28
28
|
border-left: 1px solid #E4FDB4;
|
29
29
|
border-top: 1px solid #E4FDB4;
|
30
30
|
border-right: 1px solid #B7CB90;
|
@@ -331,7 +331,7 @@ body {
|
|
331
331
|
.errorExplanation h2 {
|
332
332
|
font-size: 1.75em;
|
333
333
|
padding-left: 40px;
|
334
|
-
background: url(<%= asset_path 'admin/icons/
|
334
|
+
background: url(<%= asset_path 'admin/icons/32x32/3.png' %>) center left no-repeat; }
|
335
335
|
ul.checkbox-list {
|
336
336
|
list-style: none; }
|
337
337
|
ul.checkbox-list li {
|
@@ -406,7 +406,7 @@ body {
|
|
406
406
|
ul.taxonomy-actions li.disabled {
|
407
407
|
opacity: .5; }
|
408
408
|
h3.warning {
|
409
|
-
background-image: url(<%= asset_path 'admin/icons/
|
409
|
+
background-image: url(<%= asset_path 'admin/icons/exclamation.png' %>);
|
410
410
|
background-position: 10px 15px;
|
411
411
|
background-repeat: no-repeat;
|
412
412
|
padding-left: 35px; }
|
@@ -14,7 +14,9 @@ class Admin::LineItemsController < Admin::BaseController
|
|
14
14
|
format.html { render :partial => "admin/orders/form", :locals => {:order => @order.reload}, :layout => false }
|
15
15
|
end
|
16
16
|
else
|
17
|
-
|
17
|
+
respond_with(@line_item) do |format|
|
18
|
+
format.js { render :action => 'create', :locals => {:order => @order.reload}, :layout => false }
|
19
|
+
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
@@ -61,11 +61,17 @@ class CheckoutController < Spree::BaseController
|
|
61
61
|
def load_order
|
62
62
|
@order = current_order
|
63
63
|
redirect_to cart_path and return unless @order and @order.checkout_allowed?
|
64
|
+
raise_insufficient_quantity and return if @order.insufficient_stock_lines.present?
|
64
65
|
redirect_to cart_path and return if @order.completed?
|
65
66
|
@order.state = params[:state] if params[:state]
|
66
67
|
state_callback(:before)
|
67
68
|
end
|
68
69
|
|
70
|
+
def raise_insufficient_quantity
|
71
|
+
flash[:error] = t('spree_inventory_error_flash_for_insufficient_quantity')
|
72
|
+
redirect_to cart_path
|
73
|
+
end
|
74
|
+
|
69
75
|
def state_callback(before_or_after = :before)
|
70
76
|
method_name = :"#{before_or_after}_#{@order.state}"
|
71
77
|
send(method_name) if respond_to?(method_name, true)
|
@@ -35,8 +35,19 @@ module Spree::BaseHelper
|
|
35
35
|
|
36
36
|
# human readable list of variant options
|
37
37
|
def variant_options(v, allow_back_orders = Spree::Config[:allow_backorders], include_style = true)
|
38
|
+
ActiveSupport::Deprecation.warn("variant_options method is deprecated, and will be removed in 0.80.0", caller)
|
38
39
|
list = v.options_text
|
39
|
-
|
40
|
+
|
41
|
+
# We shouldn't show out of stock if the product is infact in stock
|
42
|
+
# or when we're not allowing backorders.
|
43
|
+
unless (allow_back_orders || v.in_stock?)
|
44
|
+
list = if include_style
|
45
|
+
content_tag(:span, "(#{t(:out_of_stock)}) #{list}", :class => "out-of-stock")
|
46
|
+
else
|
47
|
+
"#{t(:out_of_stock)} #{list}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
40
51
|
list
|
41
52
|
end
|
42
53
|
|
@@ -56,12 +67,12 @@ module Spree::BaseHelper
|
|
56
67
|
def meta_data_tags
|
57
68
|
object = instance_variable_get('@'+controller_name.singularize)
|
58
69
|
meta = { :keywords => Spree::Config[:default_meta_keywords], :description => Spree::Config[:default_meta_description] }
|
59
|
-
|
70
|
+
|
60
71
|
if object.kind_of?(ActiveRecord::Base)
|
61
72
|
meta[:keywords] = object.meta_keywords if object[:meta_keywords].present?
|
62
73
|
meta[:description] = object.meta_description if object[:meta_description].present?
|
63
74
|
end
|
64
|
-
|
75
|
+
|
65
76
|
meta.map do |name, content|
|
66
77
|
tag('meta', :name => name, :content => content)
|
67
78
|
end.join("\n")
|
@@ -88,7 +99,7 @@ module Spree::BaseHelper
|
|
88
99
|
def logo(image_path=Spree::Config[:logo])
|
89
100
|
link_to image_tag(image_path), root_path
|
90
101
|
end
|
91
|
-
|
102
|
+
|
92
103
|
def flash_messages
|
93
104
|
[:notice, :error].map do |msg_type|
|
94
105
|
if flash[msg_type]
|
@@ -98,7 +109,7 @@ module Spree::BaseHelper
|
|
98
109
|
end
|
99
110
|
end.join("\n").html_safe
|
100
111
|
end
|
101
|
-
|
112
|
+
|
102
113
|
def breadcrumbs(taxon, separator=" » ")
|
103
114
|
return "" if current_page?("/") || taxon.nil?
|
104
115
|
separator = raw(separator)
|
@@ -120,8 +131,8 @@ module Spree::BaseHelper
|
|
120
131
|
root_taxon.children.map do |taxon|
|
121
132
|
css_class = (current_taxon && current_taxon.self_and_ancestors.include?(taxon)) ? 'current' : nil
|
122
133
|
content_tag :li, :class => css_class do
|
123
|
-
link_to(taxon.name, seo_url(taxon)) +
|
124
|
-
taxons_tree(taxon, current_taxon, max_level - 1)
|
134
|
+
link_to(taxon.name, seo_url(taxon)) +
|
135
|
+
taxons_tree(taxon, current_taxon, max_level - 1)
|
125
136
|
end
|
126
137
|
end.join("\n").html_safe
|
127
138
|
end
|
@@ -131,7 +142,7 @@ module Spree::BaseHelper
|
|
131
142
|
return Country.all unless zone = Zone.find_by_name(Spree::Config[:checkout_zone])
|
132
143
|
zone.country_list
|
133
144
|
end
|
134
|
-
|
145
|
+
|
135
146
|
def format_price(price, options={})
|
136
147
|
options.assert_valid_keys(:show_vat_text)
|
137
148
|
options.reverse_merge! :show_vat_text => Spree::Config[:show_price_inc_vat]
|
@@ -142,7 +153,7 @@ module Spree::BaseHelper
|
|
142
153
|
formatted_price
|
143
154
|
end
|
144
155
|
end
|
145
|
-
|
156
|
+
|
146
157
|
# generates nested url to product based on supplied taxon
|
147
158
|
def seo_url(taxon, product = nil)
|
148
159
|
return '/t/' + taxon.permalink if product.nil?
|
@@ -150,7 +161,7 @@ module Spree::BaseHelper
|
|
150
161
|
"not used anymore. Use product_url instead. (called from #{caller[0]})"
|
151
162
|
return product_url(product)
|
152
163
|
end
|
153
|
-
|
164
|
+
|
154
165
|
def current_orders_product_count
|
155
166
|
if current_order.blank? || current_order.item_count < 1
|
156
167
|
return 0
|
data/app/models/creditcard.rb
CHANGED
@@ -2,6 +2,7 @@ class Creditcard < ActiveRecord::Base
|
|
2
2
|
has_many :payments, :as => :source
|
3
3
|
|
4
4
|
before_save :set_last_digits
|
5
|
+
after_validation :set_card_type
|
5
6
|
|
6
7
|
attr_accessor :number, :verification_value
|
7
8
|
|
@@ -23,6 +24,19 @@ class Creditcard < ActiveRecord::Base
|
|
23
24
|
self.last_digits ||= number.to_s.length <= 4 ? number : number.to_s.slice(-4..-1)
|
24
25
|
end
|
25
26
|
|
27
|
+
# cheap hack to get to the type? method from deep within ActiveMerchant without stomping on
|
28
|
+
# potentially existing methods in CreditCard
|
29
|
+
class CardDetector
|
30
|
+
class << self
|
31
|
+
include ActiveMerchant::Billing::CreditCardMethods::ClassMethods
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# sets self.cc_type while we still have the card number
|
36
|
+
def set_card_type
|
37
|
+
self.cc_type ||= CardDetector.type?(self.number)
|
38
|
+
end
|
39
|
+
|
26
40
|
def name?
|
27
41
|
first_name? && last_name?
|
28
42
|
end
|
@@ -243,7 +257,7 @@ class Creditcard < ActiveRecord::Base
|
|
243
257
|
|
244
258
|
def spree_cc_type
|
245
259
|
return "visa" if ENV['RAILS_ENV'] == "development"
|
246
|
-
self.
|
260
|
+
self.cc_type
|
247
261
|
end
|
248
262
|
|
249
263
|
# Saftey check to make sure we're not accidentally performing operations on a live gateway.
|
@@ -22,12 +22,6 @@ class InventoryUnit < ActiveRecord::Base
|
|
22
22
|
after_transition :to => 'returned', :do => :restock_variant
|
23
23
|
end
|
24
24
|
|
25
|
-
# method deprecated in favour of adjust_units (which creates & destroys units as needed).
|
26
|
-
def self.sell_units(order)
|
27
|
-
warn "[DEPRECATION] `InventoryUnits#sell_units` is deprecated. Please use `InventoryUnits#assign_opening_inventory` instead. (called from #{caller[0]})"
|
28
|
-
self.adjust_units(order)
|
29
|
-
end
|
30
|
-
|
31
25
|
# Assigns inventory to a newly completed order.
|
32
26
|
# Should only be called once during the life-cycle of an order, on transition to completed.
|
33
27
|
#
|
@@ -88,7 +82,10 @@ class InventoryUnit < ActiveRecord::Base
|
|
88
82
|
end
|
89
83
|
|
90
84
|
def self.destroy_units(order, variant, quantity)
|
91
|
-
variant_units = order.inventory_units.group_by(&:variant_id)
|
85
|
+
variant_units = order.inventory_units.group_by(&:variant_id)
|
86
|
+
return unless variant_units.include? variant.id
|
87
|
+
|
88
|
+
variant_units = variant_units[variant.id].sort_by(&:state)
|
92
89
|
|
93
90
|
quantity.times do
|
94
91
|
inventory_unit = variant_units.shift
|
@@ -97,9 +94,8 @@ class InventoryUnit < ActiveRecord::Base
|
|
97
94
|
end
|
98
95
|
|
99
96
|
def self.create_units(order, variant, sold, back_order)
|
100
|
-
if back_order > 0 && !Spree::Config[:allow_backorders]
|
101
|
-
|
102
|
-
end
|
97
|
+
return if back_order > 0 && !Spree::Config[:allow_backorders]
|
98
|
+
|
103
99
|
|
104
100
|
shipment = order.shipments.detect {|shipment| !shipment.shipped? }
|
105
101
|
|
data/app/models/line_item.rb
CHANGED
@@ -10,6 +10,8 @@ class LineItem < ActiveRecord::Base
|
|
10
10
|
validates :variant, :presence => true
|
11
11
|
validates :quantity, :numericality => { :only_integer => true, :message => I18n.t("validation.must_be_int") }
|
12
12
|
validates :price, :numericality => true
|
13
|
+
validate :stock_availability
|
14
|
+
|
13
15
|
# validate :meta_validation_of_quantities
|
14
16
|
|
15
17
|
attr_accessible :quantity
|
@@ -58,6 +60,14 @@ class LineItem < ActiveRecord::Base
|
|
58
60
|
self.quantity = 0 if self.quantity.nil? || self.quantity < 0
|
59
61
|
end
|
60
62
|
|
63
|
+
def sufficient_stock?
|
64
|
+
Spree::Config[:allow_backorders] ? true : (self.variant.on_hand >= self.quantity)
|
65
|
+
end
|
66
|
+
|
67
|
+
def insufficient_stock?
|
68
|
+
!sufficient_stock?
|
69
|
+
end
|
70
|
+
|
61
71
|
private
|
62
72
|
def update_inventory
|
63
73
|
return true unless self.order.completed?
|
@@ -89,7 +99,12 @@ class LineItem < ActiveRecord::Base
|
|
89
99
|
if order.try(:inventory_units).to_a.any?{|unit| unit.variant_id == variant_id && unit.shipped?}
|
90
100
|
errors.add :base, I18n.t("cannot_destory_line_item_as_inventory_units_have_shipped")
|
91
101
|
return false
|
92
|
-
|
93
|
-
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def stock_availability
|
106
|
+
return if sufficient_stock?
|
107
|
+
errors.add(:quantiy, "can't be greater than avaiable stock.")
|
108
|
+
end
|
94
109
|
end
|
95
110
|
|
data/app/models/order.rb
CHANGED
@@ -45,8 +45,6 @@ class Order < ActiveRecord::Base
|
|
45
45
|
|
46
46
|
make_permalink :field => :number
|
47
47
|
|
48
|
-
attr_accessor :out_of_stock_items
|
49
|
-
|
50
48
|
class_attribute :update_hooks
|
51
49
|
self.update_hooks = Set.new
|
52
50
|
|
@@ -318,7 +316,7 @@ class Order < ActiveRecord::Base
|
|
318
316
|
# Called after transition to complete state when payments will have been processed
|
319
317
|
def finalize!
|
320
318
|
update_attribute(:completed_at, Time.now)
|
321
|
-
|
319
|
+
InventoryUnit.assign_opening_inventory(self)
|
322
320
|
# lock any optional adjustments (coupon promotions, etc.)
|
323
321
|
adjustments.optional.each { |adjustment| adjustment.update_attribute("locked", true) }
|
324
322
|
OrderMailer.confirm_email(self).deliver
|
@@ -378,6 +376,10 @@ class Order < ActiveRecord::Base
|
|
378
376
|
line_items.map{|li| li.variant.product}
|
379
377
|
end
|
380
378
|
|
379
|
+
def insufficient_stock_lines
|
380
|
+
line_items.select &:insufficient_stock?
|
381
|
+
end
|
382
|
+
|
381
383
|
private
|
382
384
|
def create_user
|
383
385
|
self.email = user.email if self.user and not user.anonymous?
|
data/app/models/product.rb
CHANGED
@@ -44,18 +44,17 @@ class Product < ActiveRecord::Base
|
|
44
44
|
after_save :save_master
|
45
45
|
|
46
46
|
has_many :variants,
|
47
|
-
:conditions => ["
|
48
|
-
:order =>
|
49
|
-
|
47
|
+
:conditions => ["#{Variant.table_name}.is_master = ? AND #{Variant.table_name}.deleted_at IS NULL", false],
|
48
|
+
:order => "#{Variant.table_name}.position ASC"
|
50
49
|
|
51
50
|
has_many :variants_including_master,
|
52
51
|
:class_name => 'Variant',
|
53
|
-
:conditions => ["
|
52
|
+
:conditions => ["#{Variant.table_name}.deleted_at IS NULL"],
|
54
53
|
:dependent => :destroy
|
55
54
|
|
56
55
|
has_many :variants_with_only_master,
|
57
56
|
:class_name => 'Variant',
|
58
|
-
:conditions => ["
|
57
|
+
:conditions => ["#{Variant.table_name}.deleted_at IS NULL AND #{Variant.table_name}.is_master = ?", true],
|
59
58
|
:dependent => :destroy
|
60
59
|
|
61
60
|
|
@@ -74,30 +73,43 @@ class Product < ActiveRecord::Base
|
|
74
73
|
|
75
74
|
include ::Scopes::Product
|
76
75
|
|
77
|
-
#RAILS3 TODO - scopes are duplicated here and in
|
76
|
+
#RAILS3 TODO - scopes are duplicated here and in scopes/product.rb - can we DRY it up?
|
78
77
|
# default product scope only lists available and non-deleted products
|
79
|
-
|
78
|
+
class << self
|
79
|
+
def not_deleted
|
80
|
+
where(Product.arel_table[:deleted_at].eq(nil))
|
81
|
+
end
|
82
|
+
|
83
|
+
def available(available_on = nil)
|
84
|
+
where(Product.arel_table[:available_on].lteq(available_on || Time.zone.now ))
|
85
|
+
end
|
80
86
|
|
81
|
-
|
87
|
+
#RAILS 3 TODO - this scope doesn't match the original 2.3.x version, needs attention (but it works)
|
88
|
+
def active
|
89
|
+
not_deleted.available
|
90
|
+
end
|
82
91
|
|
83
|
-
|
84
|
-
|
92
|
+
def on_hand
|
93
|
+
where(Product.arel_table[:count_on_hand].gteq(0))
|
94
|
+
end
|
85
95
|
|
86
|
-
|
96
|
+
def id_equals(input_id)
|
97
|
+
where(Product.arel_table[:id].eq(input_id))
|
98
|
+
end
|
87
99
|
|
100
|
+
def taxons_name_eq(name)
|
101
|
+
joins(:taxons).where(Taxon.arel_table[:name].eq(name))
|
102
|
+
end
|
103
|
+
end
|
88
104
|
if (ActiveRecord::Base.connection.adapter_name == 'PostgreSQL')
|
89
|
-
if
|
90
|
-
scope :group_by_products_id, { :group => Product.column_names.map{|col_name| "
|
105
|
+
if Product.table_exists?
|
106
|
+
scope :group_by_products_id, { :group => Product.column_names.map{|col_name| "#{Product.table_name}.#{col_name}"} }
|
91
107
|
end
|
92
108
|
else
|
93
|
-
scope :group_by_products_id, { :group => "
|
109
|
+
scope :group_by_products_id, { :group => "#{Product.table_name}.id" }
|
94
110
|
end
|
95
111
|
search_methods :group_by_products_id
|
96
112
|
|
97
|
-
scope :id_equals, lambda { |input_id| where("products.id = ?", input_id) }
|
98
|
-
|
99
|
-
scope :taxons_name_eq, lambda { |name| joins(:taxons).where("taxons.name = ?", name) }
|
100
|
-
|
101
113
|
# ----------------------------------------------------------------------------------------------------------
|
102
114
|
#
|
103
115
|
# The following methods are deprecated and will be removed in a future version of Spree
|
@@ -135,7 +147,7 @@ class Product < ActiveRecord::Base
|
|
135
147
|
|
136
148
|
# returns true if the product has any variants (the master variant is not a member of the variants array)
|
137
149
|
def has_variants?
|
138
|
-
|
150
|
+
variants.any?
|
139
151
|
end
|
140
152
|
|
141
153
|
# returns the number of inventory units "on_hand" for this product
|
@@ -151,7 +163,7 @@ class Product < ActiveRecord::Base
|
|
151
163
|
|
152
164
|
# Returns true if there are inventory units (any variant) with "on_hand" state for this product
|
153
165
|
def has_stock?
|
154
|
-
master.in_stock? ||
|
166
|
+
master.in_stock? || variants.any?(&:in_stock?)
|
155
167
|
end
|
156
168
|
|
157
169
|
def tax_category
|
@@ -236,7 +248,7 @@ class Product < ActiveRecord::Base
|
|
236
248
|
end
|
237
249
|
|
238
250
|
private
|
239
|
-
|
251
|
+
|
240
252
|
def sanitize_permalink
|
241
253
|
self.permalink = self.permalink.to_url
|
242
254
|
end
|