spree_core 0.40.4 → 0.50.0

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.
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