spree_core 0.40.4 → 0.50.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (251) hide show
  1. data/README.md +6 -1
  2. data/app/controllers/admin/base_controller.rb +0 -57
  3. data/app/controllers/admin/configurations_controller.rb +1 -1
  4. data/app/controllers/admin/general_settings_controller.rb +1 -1
  5. data/app/controllers/admin/images_controller.rb +18 -8
  6. data/app/controllers/admin/inventory_settings_controller.rb +1 -1
  7. data/app/controllers/admin/mail_settings_controller.rb +1 -1
  8. data/app/controllers/admin/option_types_controller.rb +11 -0
  9. data/app/controllers/admin/orders_controller.rb +16 -20
  10. data/app/controllers/admin/overview_controller.rb +1 -1
  11. data/app/controllers/admin/payment_methods_controller.rb +6 -6
  12. data/app/controllers/admin/product_groups_controller.rb +10 -11
  13. data/app/controllers/admin/product_properties_controller.rb +5 -5
  14. data/app/controllers/admin/product_scopes_controller.rb +12 -4
  15. data/app/controllers/admin/products_controller.rb +17 -12
  16. data/app/controllers/admin/properties_controller.rb +7 -7
  17. data/app/controllers/admin/prototypes_controller.rb +15 -15
  18. data/app/controllers/admin/reports_controller.rb +12 -11
  19. data/app/controllers/admin/shipments_controller.rb +0 -5
  20. data/app/controllers/admin/shipping_categories_controller.rb +6 -6
  21. data/app/controllers/admin/shipping_methods_controller.rb +1 -2
  22. data/app/controllers/admin/states_controller.rb +12 -12
  23. data/app/controllers/admin/tax_categories_controller.rb +2 -2
  24. data/app/controllers/admin/tax_rates_controller.rb +8 -8
  25. data/app/controllers/admin/tax_settings_controller.rb +1 -1
  26. data/app/controllers/admin/taxonomies_controller.rb +3 -3
  27. data/app/controllers/admin/taxons_controller.rb +1 -8
  28. data/app/controllers/admin/trackers_controller.rb +1 -1
  29. data/app/controllers/admin/users_controller.rb +6 -9
  30. data/app/controllers/admin/variants_controller.rb +13 -2
  31. data/app/controllers/admin/zones_controller.rb +9 -9
  32. data/app/controllers/checkout_controller.rb +5 -5
  33. data/app/controllers/products_controller.rb +1 -1
  34. data/app/controllers/taxons_controller.rb +3 -1
  35. data/app/helpers/admin/navigation_helper.rb +36 -19
  36. data/app/helpers/admin/payments_helper.rb +1 -1
  37. data/app/helpers/admin/product_groups_helper.rb +1 -1
  38. data/app/helpers/admin/product_properties_helper.rb +5 -5
  39. data/app/helpers/admin/products_helper.rb +2 -2
  40. data/app/helpers/admin/users_helper.rb +5 -2
  41. data/app/helpers/hook_helper.rb +3 -3
  42. data/app/helpers/products_helper.rb +0 -19
  43. data/app/helpers/spree/base_helper.rb +33 -2
  44. data/app/helpers/taxons_helper.rb +3 -3
  45. data/app/mailers/order_mailer.rb +2 -2
  46. data/app/mailers/shipment_mailer.rb +2 -2
  47. data/app/models/address.rb +42 -9
  48. data/app/models/adjustment.rb +8 -8
  49. data/app/models/app_configuration.rb +0 -6
  50. data/app/models/billing_integration.rb +1 -1
  51. data/app/models/calculator/sales_tax.rb +3 -3
  52. data/app/models/configuration.rb +1 -1
  53. data/app/models/country.rb +4 -5
  54. data/app/models/creditcard.rb +38 -31
  55. data/app/models/gateway.rb +14 -14
  56. data/app/models/gateway/beanstream.rb +4 -4
  57. data/app/models/gateway/bogus.rb +6 -6
  58. data/app/models/gateway/braintree.rb +88 -0
  59. data/app/models/gateway/eway.rb +3 -3
  60. data/app/models/image.rb +5 -4
  61. data/app/models/option_type.rb +1 -0
  62. data/app/models/order.rb +60 -21
  63. data/app/models/payment.rb +5 -32
  64. data/app/models/preference.rb +7 -7
  65. data/app/models/product.rb +22 -7
  66. data/app/models/product_group.rb +22 -26
  67. data/app/models/product_property.rb +5 -5
  68. data/app/models/product_scope.rb +26 -6
  69. data/app/models/property.rb +1 -1
  70. data/app/models/state.rb +2 -3
  71. data/app/models/tax_category.rb +1 -0
  72. data/app/models/tax_rate.rb +1 -2
  73. data/app/models/taxon.rb +12 -10
  74. data/app/models/taxonomy.rb +7 -4
  75. data/app/models/tracker.rb +1 -1
  76. data/app/models/user.rb +4 -0
  77. data/app/models/variant.rb +1 -1
  78. data/app/models/zone.rb +1 -1
  79. data/app/models/zone_member.rb +3 -3
  80. data/app/views/admin/{shared → adjustments}/_adjustments_table.html.erb +7 -4
  81. data/app/views/admin/adjustments/edit.html.erb +1 -1
  82. data/app/views/admin/adjustments/index.html.erb +2 -2
  83. data/app/views/admin/adjustments/new.html.erb +2 -1
  84. data/app/views/admin/general_settings/edit.html.erb +4 -12
  85. data/app/views/admin/general_settings/show.html.erb +0 -5
  86. data/app/views/admin/images/index.html.erb +8 -5
  87. data/app/views/admin/inventory_settings/show.html.erb +1 -1
  88. data/app/views/admin/mail_methods/index.html.erb +4 -4
  89. data/app/views/admin/option_types/_form.html.erb +4 -4
  90. data/app/views/admin/option_types/_option_value_fields.html.erb +2 -2
  91. data/app/views/admin/option_types/edit.html.erb +4 -2
  92. data/app/views/admin/option_types/index.html.erb +5 -5
  93. data/app/views/admin/orders/_line_item.html.erb +2 -1
  94. data/app/views/admin/orders/history.html.erb +6 -2
  95. data/app/views/admin/orders/index.html.erb +22 -19
  96. data/app/views/admin/orders/show.html.erb +1 -1
  97. data/app/views/admin/orders/user.html.erb +1 -1
  98. data/app/views/admin/payment_methods/index.html.erb +7 -5
  99. data/app/views/admin/payments/_list.html.erb +3 -3
  100. data/app/views/admin/payments/index.html.erb +1 -1
  101. data/app/views/admin/payments/show.html.erb +2 -2
  102. data/app/views/admin/product_groups/edit.html.erb +7 -7
  103. data/app/views/admin/product_groups/index.html.erb +5 -3
  104. data/app/views/admin/product_groups/update.js.erb +4 -3
  105. data/app/views/admin/product_properties/_product_property_fields.html.erb +3 -3
  106. data/app/views/admin/product_properties/index.html.erb +10 -5
  107. data/app/views/admin/product_scopes/destroy.js.erb +1 -0
  108. data/app/views/admin/products/index.html.erb +32 -33
  109. data/app/views/admin/properties/_form.html.erb +2 -2
  110. data/app/views/admin/properties/index.html.erb +4 -4
  111. data/app/views/admin/prototypes/index.html.erb +4 -4
  112. data/app/views/admin/shared/_address_form.html.erb +1 -1
  113. data/app/views/admin/shared/_calculator_fields.html.erb +1 -1
  114. data/app/views/admin/shared/_destroy.js.erb +15 -2
  115. data/app/views/admin/shared/_order_tabs.html.erb +1 -1
  116. data/app/views/admin/shared/_report_criteria.html.erb +1 -1
  117. data/app/views/admin/shipments/_form.html.erb +6 -2
  118. data/app/views/admin/shipments/edit.html.erb +1 -1
  119. data/app/views/admin/shipments/index.html.erb +4 -2
  120. data/app/views/admin/shipping_methods/_form.html.erb +2 -0
  121. data/app/views/admin/shipping_methods/index.html.erb +3 -2
  122. data/app/views/admin/states/_state_list.html.erb +11 -5
  123. data/app/views/admin/tax_categories/index.html.erb +9 -4
  124. data/app/views/admin/tax_settings/show.html.erb +2 -2
  125. data/app/views/admin/taxonomies/_list.html.erb +4 -2
  126. data/app/views/admin/taxonomies/index.html.erb +2 -2
  127. data/app/views/admin/taxons/_form.html.erb +1 -1
  128. data/app/views/admin/trackers/index.html.erb +5 -5
  129. data/app/views/admin/users/_form.html.erb +3 -4
  130. data/app/views/admin/users/index.html.erb +7 -6
  131. data/app/views/admin/users/show.html.erb +3 -3
  132. data/app/views/admin/variants/index.html.erb +21 -6
  133. data/app/views/admin/zones/_form.html.erb +9 -9
  134. data/app/views/admin/zones/_member_type.html.erb +5 -5
  135. data/app/views/admin/zones/index.html.erb +7 -5
  136. data/app/views/checkout/_address.html.erb +2 -2
  137. data/app/views/checkout/_payment.html.erb +3 -6
  138. data/app/views/layouts/admin.html.erb +3 -9
  139. data/app/views/layouts/spree_application.html.erb +2 -1
  140. data/app/views/orders/_line_item.html.erb +1 -1
  141. data/app/views/orders/edit.html.erb +17 -16
  142. data/app/views/orders/show.html.erb +1 -1
  143. data/app/views/shared/_admin_head.html.erb +1 -1
  144. data/app/views/shared/_error_messages.html.erb +2 -2
  145. data/app/views/shared/_filters.html.erb +4 -4
  146. data/app/views/shared/_head.html.erb +2 -2
  147. data/app/views/shared/_nav_bar.html.erb +2 -2
  148. data/app/views/shared/_products.html.erb +4 -2
  149. data/app/views/shared/_taxonomies.html.erb +15 -8
  150. data/app/views/shipment_mailer/shipped_email.text.erb +2 -2
  151. data/config/cucumber.yml +10 -0
  152. data/config/initializers/form_builder.rb +1 -5
  153. data/config/initializers/workarounds_for_ruby19.rb +5 -5
  154. data/config/locales/en.yml +33 -6
  155. data/config/routes.rb +18 -13
  156. data/db/migrate/20090923100315_add_count_on_hand_to_variants_and_products.rb +5 -5
  157. data/db/migrate/20091213222815_creditcard_last_four_digits.rb +5 -5
  158. data/db/migrate/20100105132138_shipment_id_for_inventory_units.rb +2 -2
  159. data/db/migrate/20100209025806_create_payment_methods.rb +3 -3
  160. data/db/migrate/20100209144531_polymorphic_payments.rb +1 -1
  161. data/db/migrate/20100214212536_assign_creditcard_txns_to_payment.rb +2 -2
  162. data/db/migrate/20100224153127_deleted_at_for_payment_methods.rb +1 -1
  163. data/db/migrate/20100506185838_add_description_to_taxons.rb +1 -1
  164. data/db/migrate/20100816212146_shipping_method_id_for_orders.rb +1 -1
  165. data/db/migrate/20101026184808_migrate_checkout_to_orders.rb +2 -2
  166. data/db/migrate/20101223215658_add_position_to_variants.rb +9 -0
  167. data/db/migrate/20110110130847_add_next_state_to_state_events.rb +9 -0
  168. data/db/migrate/20110111122537_add_position_to_option_types.rb +9 -0
  169. data/db/migrate/20110314192118_remove_trailing_slashes_in_taxon_permalinks.rb +17 -0
  170. data/lib/custom_fixtures.rb +1 -1
  171. data/lib/{seo_assist.rb → middleware/seo_assist.rb} +14 -8
  172. data/lib/product_filters.rb +49 -43
  173. data/lib/redirect_legacy_product_url.rb +5 -5
  174. data/lib/scopes.rb +2 -2
  175. data/lib/scopes/dynamic.rb +9 -16
  176. data/lib/scopes/product.rb +33 -16
  177. data/lib/scopes/variant.rb +4 -3
  178. data/lib/spree/calculated_adjustments.rb +5 -2
  179. data/lib/spree/config.rb +2 -0
  180. data/lib/spree/current_order.rb +4 -4
  181. data/lib/spree/mail_settings.rb +3 -2
  182. data/lib/spree/search/base.rb +9 -10
  183. data/lib/spree_base.rb +22 -23
  184. data/lib/spree_core.rb +10 -69
  185. data/lib/spree_core/authorize_net_cim_hack.rb +1 -1
  186. data/lib/spree_core/delegate_belongs_to.rb +18 -24
  187. data/lib/spree_core/enumerable_constants.rb +38 -38
  188. data/lib/spree_core/find_by_param.rb +8 -6
  189. data/lib/spree_core/preferences/preference_definition.rb +7 -7
  190. data/lib/spree_core/railtie.rb +58 -0
  191. data/lib/spree_core/ssl_requirement.rb +4 -3
  192. data/lib/spree_core/testing_support/factories.rb +13 -0
  193. data/lib/spree_core/testing_support/factories/address_factory.rb +20 -0
  194. data/lib/spree_core/testing_support/factories/adjustment_factory.rb +6 -0
  195. data/lib/spree_core/testing_support/factories/calculator_factory.rb +5 -0
  196. data/lib/spree_core/testing_support/factories/configuraion_factory.rb +4 -0
  197. data/lib/spree_core/testing_support/factories/country_factory.rb +7 -0
  198. data/lib/spree_core/testing_support/factories/creditcard_factory.rb +11 -0
  199. data/lib/spree_core/testing_support/factories/inventory_unit_factory.rb +7 -0
  200. data/lib/spree_core/testing_support/factories/line_item_factory.rb +8 -0
  201. data/lib/spree_core/testing_support/factories/mail_method_factory.rb +4 -0
  202. data/lib/spree_core/testing_support/factories/options_factory.rb +10 -0
  203. data/lib/spree_core/testing_support/factories/order_factory.rb +18 -0
  204. data/lib/spree_core/testing_support/factories/payment_factory.rb +26 -0
  205. data/lib/spree_core/testing_support/factories/payment_method_factory.rb +17 -0
  206. data/lib/spree_core/testing_support/factories/product_factory.rb +16 -0
  207. data/lib/spree_core/testing_support/factories/product_group_factory.rb +3 -0
  208. data/lib/spree_core/testing_support/factories/product_option_type_factory.rb +4 -0
  209. data/lib/spree_core/testing_support/factories/product_property_factory.rb +4 -0
  210. data/lib/spree_core/testing_support/factories/product_scope_factory.rb +6 -0
  211. data/lib/spree_core/testing_support/factories/property_factory.rb +4 -0
  212. data/lib/spree_core/testing_support/factories/prototype_factory.rb +4 -0
  213. data/lib/spree_core/testing_support/factories/return_authorization_factory.rb +8 -0
  214. data/lib/spree_core/testing_support/factories/role_factory.rb +9 -0
  215. data/lib/spree_core/testing_support/factories/shipment_factory.rb +9 -0
  216. data/lib/spree_core/testing_support/factories/shipping_category_factory.rb +5 -0
  217. data/lib/spree_core/testing_support/factories/shipping_method_factory.rb +7 -0
  218. data/lib/spree_core/testing_support/factories/state_factory.rb +11 -0
  219. data/lib/spree_core/testing_support/factories/tax_category_factory.rb +8 -0
  220. data/lib/spree_core/testing_support/factories/tax_rate_factory.rb +5 -0
  221. data/lib/spree_core/testing_support/factories/taxon_factory.rb +5 -0
  222. data/lib/spree_core/testing_support/factories/taxonomy_factory.rb +3 -0
  223. data/lib/spree_core/testing_support/factories/tracker_factory.rb +5 -0
  224. data/lib/spree_core/testing_support/factories/user_factory.rb +15 -0
  225. data/lib/spree_core/testing_support/factories/variant_factory.rb +14 -0
  226. data/lib/spree_core/testing_support/factories/zone_factory.rb +18 -0
  227. data/lib/spree_core/theme_support/hook.rb +1 -1
  228. data/lib/spree_core/theme_support/more_patches.rb +20 -20
  229. data/lib/spree_core/version.rb +5 -0
  230. data/lib/tasks/common.rb +30 -0
  231. data/lib/tasks/install.rake +1 -1
  232. data/lib/tasks/rake_util.rb +19 -0
  233. data/lib/tasks/taxon.rake +14 -0
  234. data/public/images/reorder.jpg +0 -0
  235. data/public/javascripts/admin.js +0 -6
  236. data/public/javascripts/admin/unobtrusive_handlers.js +28 -0
  237. data/public/javascripts/checkout.js +3 -3
  238. data/public/stylesheets/admin/admin-forms.css +1 -6
  239. data/public/stylesheets/admin/admin.css +0 -28
  240. data/public/stylesheets/screen.css +0 -280
  241. metadata +81 -43
  242. data/app/controllers/countries_controller.rb +0 -11
  243. data/app/models/spree/alert.rb +0 -13
  244. data/app/models/state_monitor.rb +0 -25
  245. data/app/views/admin/shared/_alert.html.erb +0 -6
  246. data/app/views/countries/index.js.erb +0 -1
  247. data/app/views/shared/_doc_and_xmlns.html.erb +0 -2
  248. data/app/views/users/edit.html.erb +0 -9
  249. data/app/views/users/show.html.erb +0 -46
  250. data/lib/spree_core/validation_group.rb +0 -143
  251. data/public/stylesheets/scaffold.css +0 -54
@@ -0,0 +1,9 @@
1
+ class AddNextStateToStateEvents < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :state_events, :next_state, :string
4
+ end
5
+
6
+ def self.down
7
+ remove_column :state_events, :next_state
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class AddPositionToOptionTypes < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :option_types, :position, :integer, :null => false, :default => 0
4
+ end
5
+
6
+ def self.down
7
+ remove_column :option_types, :position
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ class RemoveTrailingSlashesInTaxonPermalinks < ActiveRecord::Migration
2
+ def self.up
3
+ Taxon.find_each(:conditions => {}) do |t|
4
+ if t.permalink && t.permalink[-1..-1] == '/'
5
+ t.update_attribute(:permalink, t.permalink[0...-1])
6
+ end
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ Taxon.find_each(:conditions => {}) do |t|
12
+ if t.permalink && t.permalink[-1..-1] != '/'
13
+ t.update_attribute(:permalink, t.permalink + '/')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -4,4 +4,4 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
4
4
  def delete_existing_fixtures
5
5
  # do nothing - we're intentionally not emptying the database since it has some structural data in it
6
6
  end
7
- end
7
+ end
@@ -8,23 +8,29 @@ class SeoAssist
8
8
  request = Rack::Request.new(env)
9
9
  params = request.params
10
10
  taxon_id = params['taxon']
11
- if !taxon_id.blank? && !taxon_id.is_a?(Hash) && @taxon = Taxon.find(taxon_id)
11
+
12
+ #redirect requests using taxon id's to their permalinks
13
+ if !taxon_id.blank? && !taxon_id.is_a?(Hash) && taxon = Taxon.find(taxon_id)
12
14
  params.delete('taxon')
13
- query = build_query(params)
14
- permalink = @taxon.permalink[0...-1] #ensures no trailing / for taxon urls
15
- return [301, { 'Location'=> "/t/#{permalink}?#{query}" }, []]
15
+
16
+ return build_response(params, "/t/#{taxon.permalink}" )
16
17
  elsif env["PATH_INFO"] =~ /^\/(t|products)(\/\S+)?\/$/
17
18
  #ensures no trailing / for taxon and product urls
18
- query = build_query(params)
19
- new_location = env["PATH_INFO"][0...-1]
20
- new_location += '?' + query unless query.blank?
21
- return [301, { 'Location'=> new_location }, []]
19
+
20
+ return build_response(params, env["PATH_INFO"][0...-1])
22
21
  end
22
+
23
23
  @app.call(env)
24
24
  end
25
25
 
26
26
  private
27
27
 
28
+ def build_response(params, location)
29
+ query = build_query(params)
30
+ location += '?' + query unless query.blank?
31
+ [301, { 'Location'=> location }, []]
32
+ end
33
+
28
34
  def build_query(params)
29
35
  params.map { |k, v|
30
36
  if v.class == Array
@@ -5,7 +5,7 @@
5
5
  # set up some basic filters for use with products
6
6
  #
7
7
  # Each filter has two parts
8
- # * a parametrized named scope which expects a list of labels
8
+ # * a parametrized named scope which expects a list of labels
9
9
  # * an object which describes/defines the filter
10
10
  #
11
11
  # The filter description has three components
@@ -14,26 +14,28 @@
14
14
  # * a mapping of presentation labels to the relevant condition (in the context of the named scope)
15
15
  # * an optional list of labels and values (for use with object selection - see taxons examples below)
16
16
  #
17
- # The named scopes here have a suffix '_any', following SearchLogic's convention for a
17
+ # The named scopes here have a suffix '_any', following SearchLogic's convention for a
18
18
  # scope which returns results which match any of the inputs. This is purely a convention,
19
- # but might be a useful reminder.
19
+ # but might be a useful reminder.
20
20
  #
21
- # When creating a form, the name of the checkbox group for a filter F should be
22
- # the name of F's scope with [] appended, eg "price_range_any[]", and for
21
+ # When creating a form, the name of the checkbox group for a filter F should be
22
+ # the name of F's scope with [] appended, eg "price_range_any[]", and for
23
23
  # each label you should have a checkbox with the label as its value. On submission,
24
24
  # Rails will send the action a hash containing (among other things) an array named
25
- # after the scope whose values are the active labels.
25
+ # after the scope whose values are the active labels.
26
26
  #
27
27
  # SearchLogic will then convert this array to a call to the named scope with the array
28
28
  # contents, and the named scope will build a query with the disjunction of the conditions
29
- # relating to the labels, all relative to the scope's context.
29
+ # relating to the labels, all relative to the scope's context.
30
30
  #
31
- # The details of how/when filters are used is a detail for specific models (eg products
31
+ # The details of how/when filters are used is a detail for specific models (eg products
32
32
  # or taxons), eg see the taxon model/controller.
33
33
 
34
34
  # See specific filters below for concrete examples.
35
35
 
36
-
36
+ # This module is included by Taxon. In development mode that inclusion does not
37
+ # happen until Taxon class is loaded. Ensure that Taxon class is loaded before
38
+ # you try something like Product.price_range_any
37
39
  module ProductFilters
38
40
 
39
41
  # Example: filtering by price
@@ -42,8 +44,11 @@ module ProductFilters
42
44
  # we can access the field right away
43
45
  # The filter identifies which scope to use, then sets the conditions for each price range
44
46
  #
47
+ # If user checks off three different price ranges then the argument passed to
48
+ # below scope would be something like ["$10 - $15", "$15 - $18", "$18 - $20"]
49
+ #
45
50
  Product.scope :price_range_any,
46
- lambda {|opts|
51
+ lambda {|*opts|
47
52
  conds = opts.map {|o| ProductFilters.price_filter[:conds][o]}.reject {|c| c.nil?}
48
53
  Product.scoped(:joins => :master).conditions_any(conds)
49
54
  }
@@ -63,27 +68,27 @@ module ProductFilters
63
68
 
64
69
 
65
70
  # Example: filtering by possible brands
66
- #
71
+ #
67
72
  # First, we define the scope. Two interesting points here: (a) we run our conditions
68
- # in the scope where the info for the 'brand' property has been loaded; and (b)
69
- # because we may want to filter by other properties too, we give this part of the
73
+ # in the scope where the info for the 'brand' property has been loaded; and (b)
74
+ # because we may want to filter by other properties too, we give this part of the
70
75
  # query a unique name (which must be used in the associated conditions too).
71
76
  #
72
- # Secondly, the filter. Instead of a static list of values, we pull out all existing
77
+ # Secondly, the filter. Instead of a static list of values, we pull out all existing
73
78
  # brands from the db, and then build conditions which test for string equality on
74
79
  # the (uniquely named) field "p_brand.value". There's also a test for brand info
75
- # being blank: note that this relies on with_property doing a left outer join
76
- # rather than an inner join.
80
+ # being blank: note that this relies on with_property doing a left outer join
81
+ # rather than an inner join.
77
82
 
78
83
  if Property.table_exists? && @@brand_property = Property.find_by_name("brand")
79
84
  Product.scope :brand_any,
80
- lambda {|opts|
81
- conds = opts.map {|o| ProductFilters.brand_filter[:conds][o]}.reject {|c| c.nil?}
85
+ lambda {|*opts|
86
+ conds = opts.map {|o| ProductFilters.brand_filter[:conds][o]}.reject {|c| c.nil?}
82
87
  Product.with_property("brand").conditions_any(conds)
83
- }
88
+ }
84
89
 
85
90
  def ProductFilters.brand_filter
86
- brands = ProductProperty.find_all_by_property_id(@@brand_property).map(&:value).uniq
91
+ brands = ProductProperty.find_all_by_property_id(@@brand_property).map(&:value).compact.uniq
87
92
  conds = Hash[*brands.map {|b| [b, "product_properties.value = '#{b}'"]}.flatten]
88
93
  { :name => "Brands",
89
94
  :scope => :brand_any,
@@ -95,34 +100,34 @@ module ProductFilters
95
100
 
96
101
  # Example: a parametrized filter
97
102
  # The filter above may show brands which aren't applicable to the current taxon,
98
- # so this one only shows the brands that are relevant to a particular taxon and
103
+ # so this one only shows the brands that are relevant to a particular taxon and
99
104
  # its descendants.
100
105
  #
101
- # We don't have to give a new scope since the conditions here are a subset of the
102
- # more general filter, so decoding will still work - as long as the filters on a
103
- # page all have unique names (ie, you can't use the two brand filters together
106
+ # We don't have to give a new scope since the conditions here are a subset of the
107
+ # more general filter, so decoding will still work - as long as the filters on a
108
+ # page all have unique names (ie, you can't use the two brand filters together
104
109
  # if they use the same scope). To be safe, the code uses a copy of the scope.
105
110
  #
106
- # HOWEVER: what happens if we want a more precise scope? we can't pass
107
- # parametrized scope names to SearchLogic, only atomic names, so couldn't ask
108
- # for taxon T's customized filter to be used. BUT: we can arrange for the form
109
- # to pass back a hash instead of an array, where the key acts as the (taxon)
111
+ # HOWEVER: what happens if we want a more precise scope? we can't pass
112
+ # parametrized scope names to SearchLogic, only atomic names, so couldn't ask
113
+ # for taxon T's customized filter to be used. BUT: we can arrange for the form
114
+ # to pass back a hash instead of an array, where the key acts as the (taxon)
110
115
  # parameter and value is its label array, and then get a modified named scope
111
- # to get its conditions from a particular filter.
116
+ # to get its conditions from a particular filter.
112
117
  #
113
- # The brand-finding code can be simplified if a few more named scopes were added to
114
- # the product properties model.
118
+ # The brand-finding code can be simplified if a few more named scopes were added to
119
+ # the product properties model.
115
120
 
116
- if Property.table_exists? && @@brand_property
121
+ if Property.table_exists? && @@brand_property
117
122
  Product.scope :selective_brand_any, lambda {|opts| Product.brand_any(opts) }
118
123
 
119
124
  def ProductFilters.selective_brand_filter(taxon = nil)
120
- if taxon.nil?
121
- taxon = Taxonomy.first.root
122
- end
125
+ if taxon.nil?
126
+ taxon = Taxonomy.first.root
127
+ end
123
128
  all_brands = ProductProperty.find_all_by_property_id(@@brand_property).map(&:value).uniq
124
129
  scope = ProductProperty.scoped(:conditions => ["property_id = ?", @@brand_property]).
125
- scoped(:joins => {:product => :taxons},
130
+ scoped(:joins => {:product => :taxons},
126
131
  :conditions => ["taxons.id in (?)", [taxon] + taxon.descendants])
127
132
  brands = scope.map {|p| p.value}
128
133
 
@@ -136,17 +141,17 @@ module ProductFilters
136
141
 
137
142
 
138
143
  # Provide filtering on the immediate children of a taxon
139
- #
144
+ #
140
145
  # This doesn't fit the pattern of the examples above, so there's a few changes.
141
- # Firstly, it uses an existing scope which was not built for filtering - and so
142
- # has no need of a conditions mapping, and secondly, it has a mapping of name
143
- # to the argument type expected by the other scope.
146
+ # Firstly, it uses an existing scope which was not built for filtering - and so
147
+ # has no need of a conditions mapping, and secondly, it has a mapping of name
148
+ # to the argument type expected by the other scope.
144
149
  #
145
- # This technique is useful for filtering on objects (by passing ids) or with a
150
+ # This technique is useful for filtering on objects (by passing ids) or with a
146
151
  # scope that can be used directly (eg. testing only ever on a single property).
147
- #
152
+ #
148
153
  # This scope selects products in any of the active taxons or their children.
149
- #
154
+ #
150
155
  def ProductFilters.taxons_below(taxon)
151
156
  return ProductFilters.all_taxons if taxon.nil?
152
157
  { :name => "Taxons under " + taxon.name,
@@ -170,4 +175,5 @@ module ProductFilters
170
175
  :conds => nil # not needed
171
176
  }
172
177
  end
178
+
173
179
  end
@@ -2,12 +2,12 @@ class RedirectLegacyProductUrl
2
2
  def initialize(app)
3
3
  @app = app
4
4
  end
5
-
6
- def call(env)
7
- if env["PATH_INFO"] =~ %r{/t/.+/p/(.+)}
8
- return [301, {'Location'=> "/products/#{$1}" }, []]
5
+
6
+ def call(env)
7
+ if env["PATH_INFO"] =~ %r{/t/.+/p/(.+)}
8
+ return [301, {'Location'=> "/products/#{$1}" }, []]
9
9
  end
10
10
  @app.call(env)
11
11
  end
12
-
12
+
13
13
  end
data/lib/scopes.rb CHANGED
@@ -41,7 +41,7 @@ module Scopes
41
41
  scopes.each_pair do |scope_name, targs|
42
42
  hashed_args = {}
43
43
  targs.each{|v| hashed_args[v.to_s] = v.to_s.humanize}
44
-
44
+
45
45
  result['scopes'][scope_name.to_s] = {
46
46
  'name' => scope_name.to_s.humanize,
47
47
  'description' => "",
@@ -66,4 +66,4 @@ end
66
66
  # def to_sql
67
67
  # construct_finder_sql({})
68
68
  # end
69
- # end
69
+ # end
@@ -1,3 +1,4 @@
1
+ # This module is extended by ProductScope
1
2
  module Scopes::Dynamic
2
3
  module_function
3
4
 
@@ -9,30 +10,22 @@ module Scopes::Dynamic
9
10
 
10
11
  # Price based scopes
11
12
  all_prices = products.map(&:price).sort
13
+
12
14
  ranges = [Math.log(products.length).floor, scope_limit].max
15
+
13
16
  if ranges >= 2
14
17
  l = all_prices.length / ranges
15
- scopes << ProductScope.new({
16
- :name => "master_price_lte",
17
- :arguments => [all_prices[l]]
18
- })
18
+ scopes << ProductScope.new({:name => "master_price_lte", :arguments => [all_prices[l]] })
19
+
19
20
  (ranges - 2).times do |x|
20
- scopes << ProductScope.new({
21
- :name => "price_between",
22
- :arguments => [
23
- all_prices[l*(x+1)+1],
24
- all_prices[l*(x+2)]
25
- ]
26
- })
21
+ scopes << ProductScope.new({:name => "price_between",
22
+ :arguments => [ all_prices[l*(x+1)+1], all_prices[l*(x+2)] ] })
27
23
  end
28
- scopes << ProductScope.new({
29
- :name => "master_price_gte",
30
- :arguments => [all_prices[l*(ranges-1)+1]]
31
- })
24
+
25
+ scopes << ProductScope.new({:name => "master_price_gte", :arguments => [all_prices[l*(ranges-1)+1]] })
32
26
  end
33
27
 
34
28
  scopes
35
29
  end
36
30
 
37
-
38
31
  end
@@ -40,6 +40,22 @@ module Scopes::Product
40
40
  :descend_by_popularity,
41
41
  ]
42
42
 
43
+ ORDERING.each do |name|
44
+ next if %w(asecend_by_master_price descend_by_master_price).include?(name.to_s)
45
+ r = name.to_s.match(/(.*)_by_(.*)/)
46
+
47
+ order_text = "products.#{r[2]} "
48
+ order_text << ((r[1] == 'ascend') ? "asc" : "desc")
49
+
50
+ Product.send(:scope, name.to_s, Product.send(:relation).order(order_text) )
51
+ end
52
+
53
+ ::Product.scope :ascend_by_master_price, lambda {
54
+ Product.joins(:variants_with_only_master).order('variants.price asc') }
55
+
56
+ ::Product.scope :descend_by_master_price, lambda {
57
+ Product.joins(:variants_with_only_master).order('variants.price desc') }
58
+
43
59
  ATTRIBUTE_HELPER_METHODS = {
44
60
  :with_ids => :product_picker_field
45
61
  }
@@ -47,9 +63,10 @@ module Scopes::Product
47
63
  # Ryan Bates - http://railscasts.com/episodes/112
48
64
  # general merging of conditions, names following the searchlogic pattern
49
65
  ::Product.scope :conditions, lambda { |*args| {:conditions => args}}
66
+
50
67
  # conditions_all is a more descriptively named enhancement of the above
51
- ::Product.scope :conditions_all, lambda { |*args| {:conditions => [args].flatten}}
52
-
68
+ ::Product.scope :conditions_all, lambda { |*args| {:conditions => [args].flatten}}
69
+
53
70
  # forming the disjunction of a list of conditions (as strings)
54
71
  ::Product.scope :conditions_any, lambda { |*args|
55
72
  args = [args].flatten
@@ -58,21 +75,19 @@ module Scopes::Product
58
75
  }
59
76
 
60
77
 
78
+ ::Product.scope :price_between, lambda { |low, high|
79
+ { :joins => :master, :conditions => ["variants.price BETWEEN ? AND ?", low, high] }
80
+ }
61
81
 
62
- #RAILS3 TODO - scopes are duplicated here and in model/product.rb - can we DRY it up?
63
- # default product scope only lists available and non-deleted products
64
- # ::Product.scope :not_deleted, lambda { where("products.deleted_at is null") }
65
- # ::Product.scope :available, lambda { |*args|
66
- # where("products.available_on <= ?", args.first || Time.zone.now)
67
- # }
68
- # ::Product.scope :active, lambda { |*args|
69
- # Product.not_deleted.available(args.first).scope(:find)
70
- # }
82
+ ::Product.scope :master_price_lte, lambda { |price|
83
+ { :joins => :master, :conditions => ["variants.price <= ?", price] }
84
+ }
71
85
 
72
- ::Product.scope :price_between, lambda {|low,high|
73
- { :joins => :master, :conditions => ["variants.price BETWEEN ? AND ?", low, high] }
86
+ ::Product.scope :master_price_gte, lambda { |price|
87
+ { :joins => :master, :conditions => ["variants.price >= ?", price] }
74
88
  }
75
89
 
90
+
76
91
  # This scope selects products in taxon AND all its descendants
77
92
  # If you need products only within one taxon use
78
93
  #
@@ -176,11 +191,11 @@ module Scopes::Product
176
191
  Product.scope :in_name, lambda{|words|
177
192
  Product.like_any([:name], prepare_words(words))
178
193
  }
179
-
194
+
180
195
  Product.scope :in_name_or_keywords, lambda{|words|
181
196
  Product.like_any([:name, :meta_keywords], prepare_words(words))
182
197
  }
183
-
198
+
184
199
  Product.scope :in_name_or_description, lambda{|words|
185
200
  Product.like_any([:name, :description, :meta_description, :meta_keywords], prepare_words(words))
186
201
  }
@@ -218,8 +233,10 @@ SQL
218
233
  }
219
234
  }
220
235
 
221
- # Produce an array of keywords for use in scopes. Always return array with at least an empty string to avoid SQL errors
236
+ # Produce an array of keywords for use in scopes.
237
+ # Always return array with at least an empty string to avoid SQL errors
222
238
  def self.prepare_words(words)
239
+ return [''] if words.blank?
223
240
  a = words.split(/[,\s]/).map(&:strip)
224
241
  a.any? ? a : ['']
225
242
  end
@@ -1,10 +1,11 @@
1
1
  module Scopes::Variant
2
- # WARNING tested only under sqlite and postgresql
2
+
3
+ #FIXME WARNING tested only under sqlite and postgresql
3
4
  Variant.scope :descend_by_popularity, lambda{
4
5
  order('COALESCE((SELECT COUNT(*) FROM line_items GROUP BY line_items.variant_id HAVING line_items.variant_id = variants.id), 0) DESC')
5
6
  }
6
7
 
7
- # for selecting variants with an option value
8
+ # for selecting variants with an option value
8
9
  # no option type given since the value implies an option type
9
10
  # this scope can be chained repeatedly, since the join name is unique
10
11
  Variant.scope :has_option, lambda {|opt|
@@ -13,5 +14,5 @@ module Scopes::Variant
13
14
  :conditions => ["#{tbl}.option_value_id = (?)", opt]
14
15
  }
15
16
  }
16
-
17
+
17
18
  end