trollied 0.1.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 (144) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +294 -0
  4. data/README.rdoc +96 -0
  5. data/Rakefile +55 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/line_items_controller.rb +168 -0
  8. data/app/controllers/notes_controller.rb +121 -0
  9. data/app/controllers/orders_controller.rb +210 -0
  10. data/app/controllers/trolleys_controller.rb +26 -0
  11. data/app/helpers/gets_trollied_helper.rb +31 -0
  12. data/app/helpers/line_items_helper.rb +5 -0
  13. data/app/helpers/orders_helper.rb +184 -0
  14. data/app/models/line_item.rb +14 -0
  15. data/app/models/note.rb +15 -0
  16. data/app/models/order.rb +29 -0
  17. data/app/models/trolley.rb +51 -0
  18. data/app/views/line_items/_form.html.erb +8 -0
  19. data/app/views/line_items/_line_item.html.erb +12 -0
  20. data/app/views/line_items/new.html.erb +11 -0
  21. data/app/views/notes/_form.html.erb +7 -0
  22. data/app/views/notes/_new.html.erb +14 -0
  23. data/app/views/notes/_note.html.erb +4 -0
  24. data/app/views/notes/index.html.erb +13 -0
  25. data/app/views/notes/new.html.erb +5 -0
  26. data/app/views/orders/_date_pickers.html.erb +15 -0
  27. data/app/views/orders/_order.html.erb +53 -0
  28. data/app/views/orders/index.html.erb +22 -0
  29. data/app/views/orders/show.html.erb +14 -0
  30. data/app/views/trolleys/_orders.html.erb +13 -0
  31. data/app/views/trolleys/show.html.erb +16 -0
  32. data/config/locales/en.yml +115 -0
  33. data/config/routes.rb +8 -0
  34. data/generators/trollied_migrations/USAGE +2 -0
  35. data/generators/trollied_migrations/templates/line_items_migration.rb +16 -0
  36. data/generators/trollied_migrations/templates/notes_migration.rb +15 -0
  37. data/generators/trollied_migrations/templates/orders_migration.rb +15 -0
  38. data/generators/trollied_migrations/templates/trolleys_migration.rb +14 -0
  39. data/generators/trollied_migrations/trollied_migrations_generator.rb +13 -0
  40. data/lib/gets_trollied.rb +38 -0
  41. data/lib/gets_trollied_controller_helpers.rb +29 -0
  42. data/lib/has_trolley.rb +42 -0
  43. data/lib/has_trolley_controller_helpers.rb +43 -0
  44. data/lib/order_status.rb +121 -0
  45. data/lib/trollied.rb +5 -0
  46. data/rails/init.rb +10 -0
  47. data/test/full_2_3_5_app_with_tests/.gitignore +27 -0
  48. data/test/full_2_3_5_app_with_tests/README +1 -0
  49. data/test/full_2_3_5_app_with_tests/Rakefile +10 -0
  50. data/test/full_2_3_5_app_with_tests/app/controllers/application_controller.rb +14 -0
  51. data/test/full_2_3_5_app_with_tests/app/controllers/items_controller.rb +85 -0
  52. data/test/full_2_3_5_app_with_tests/app/controllers/user_controller.rb +2 -0
  53. data/test/full_2_3_5_app_with_tests/app/helpers/application_helper.rb +3 -0
  54. data/test/full_2_3_5_app_with_tests/app/helpers/items_helper.rb +2 -0
  55. data/test/full_2_3_5_app_with_tests/app/helpers/user_helper.rb +2 -0
  56. data/test/full_2_3_5_app_with_tests/app/models/item.rb +4 -0
  57. data/test/full_2_3_5_app_with_tests/app/models/user.rb +3 -0
  58. data/test/full_2_3_5_app_with_tests/app/views/items/edit.html.erb +20 -0
  59. data/test/full_2_3_5_app_with_tests/app/views/items/index.html.erb +22 -0
  60. data/test/full_2_3_5_app_with_tests/app/views/items/new.html.erb +19 -0
  61. data/test/full_2_3_5_app_with_tests/app/views/items/show.html.erb +13 -0
  62. data/test/full_2_3_5_app_with_tests/app/views/layouts/application.html.erb +19 -0
  63. data/test/full_2_3_5_app_with_tests/app/views/layouts/items.html.erb +19 -0
  64. data/test/full_2_3_5_app_with_tests/config/boot.rb +110 -0
  65. data/test/full_2_3_5_app_with_tests/config/database.yml +22 -0
  66. data/test/full_2_3_5_app_with_tests/config/environment.rb +57 -0
  67. data/test/full_2_3_5_app_with_tests/config/environments/development.rb +17 -0
  68. data/test/full_2_3_5_app_with_tests/config/environments/production.rb +28 -0
  69. data/test/full_2_3_5_app_with_tests/config/environments/test.rb +28 -0
  70. data/test/full_2_3_5_app_with_tests/config/initializers/backtrace_silencers.rb +7 -0
  71. data/test/full_2_3_5_app_with_tests/config/initializers/inflections.rb +10 -0
  72. data/test/full_2_3_5_app_with_tests/config/initializers/mime_types.rb +5 -0
  73. data/test/full_2_3_5_app_with_tests/config/initializers/new_rails_defaults.rb +21 -0
  74. data/test/full_2_3_5_app_with_tests/config/initializers/session_store.rb +15 -0
  75. data/test/full_2_3_5_app_with_tests/config/locales/en.yml +4 -0
  76. data/test/full_2_3_5_app_with_tests/config/locales/fr.yml +4 -0
  77. data/test/full_2_3_5_app_with_tests/config/locales/zh.yml +4 -0
  78. data/test/full_2_3_5_app_with_tests/config/locales.yml +5 -0
  79. data/test/full_2_3_5_app_with_tests/config/routes.rb +8 -0
  80. data/test/full_2_3_5_app_with_tests/config.ru +6 -0
  81. data/test/full_2_3_5_app_with_tests/db/migrate/20100407010602_create_items.rb +13 -0
  82. data/test/full_2_3_5_app_with_tests/db/migrate/20110426030509_create_users.rb +13 -0
  83. data/test/full_2_3_5_app_with_tests/db/migrate/20110426033906_create_trolleys.rb +14 -0
  84. data/test/full_2_3_5_app_with_tests/db/migrate/20110426033907_create_orders.rb +15 -0
  85. data/test/full_2_3_5_app_with_tests/db/migrate/20110426033908_create_line_items.rb +16 -0
  86. data/test/full_2_3_5_app_with_tests/db/migrate/20110615234040_create_notes.rb +15 -0
  87. data/test/full_2_3_5_app_with_tests/db/schema.rb +56 -0
  88. data/test/full_2_3_5_app_with_tests/db/seeds.rb +7 -0
  89. data/test/full_2_3_5_app_with_tests/doc/README_FOR_APP +2 -0
  90. data/test/full_2_3_5_app_with_tests/public/404.html +30 -0
  91. data/test/full_2_3_5_app_with_tests/public/422.html +30 -0
  92. data/test/full_2_3_5_app_with_tests/public/500.html +30 -0
  93. data/test/full_2_3_5_app_with_tests/public/favicon.ico +0 -0
  94. data/test/full_2_3_5_app_with_tests/public/images/rails.png +0 -0
  95. data/test/full_2_3_5_app_with_tests/public/index.html +275 -0
  96. data/test/full_2_3_5_app_with_tests/public/javascripts/application.js +2 -0
  97. data/test/full_2_3_5_app_with_tests/public/javascripts/controls.js +963 -0
  98. data/test/full_2_3_5_app_with_tests/public/javascripts/dragdrop.js +973 -0
  99. data/test/full_2_3_5_app_with_tests/public/javascripts/effects.js +1128 -0
  100. data/test/full_2_3_5_app_with_tests/public/javascripts/prototype.js +4320 -0
  101. data/test/full_2_3_5_app_with_tests/public/robots.txt +5 -0
  102. data/test/full_2_3_5_app_with_tests/public/stylesheets/scaffold.css +54 -0
  103. data/test/full_2_3_5_app_with_tests/script/about +4 -0
  104. data/test/full_2_3_5_app_with_tests/script/console +3 -0
  105. data/test/full_2_3_5_app_with_tests/script/dbconsole +3 -0
  106. data/test/full_2_3_5_app_with_tests/script/destroy +3 -0
  107. data/test/full_2_3_5_app_with_tests/script/generate +3 -0
  108. data/test/full_2_3_5_app_with_tests/script/performance/benchmarker +3 -0
  109. data/test/full_2_3_5_app_with_tests/script/performance/profiler +3 -0
  110. data/test/full_2_3_5_app_with_tests/script/plugin +3 -0
  111. data/test/full_2_3_5_app_with_tests/script/runner +3 -0
  112. data/test/full_2_3_5_app_with_tests/script/server +3 -0
  113. data/test/full_2_3_5_app_with_tests/test/factories.rb +22 -0
  114. data/test/full_2_3_5_app_with_tests/test/functional/line_items_controller_test.rb +69 -0
  115. data/test/full_2_3_5_app_with_tests/test/functional/orders_controller_test.rb +75 -0
  116. data/test/full_2_3_5_app_with_tests/test/integration/gets_trollied_test.rb +38 -0
  117. data/test/full_2_3_5_app_with_tests/test/integration/order_test.rb +79 -0
  118. data/test/full_2_3_5_app_with_tests/test/integration/trolley_test.rb +71 -0
  119. data/test/full_2_3_5_app_with_tests/test/performance/browsing_test.rb +9 -0
  120. data/test/full_2_3_5_app_with_tests/test/selenium.rb +83 -0
  121. data/test/full_2_3_5_app_with_tests/test/test_helper.rb +54 -0
  122. data/test/full_2_3_5_app_with_tests/test/unit/gets_trollied_test.rb +29 -0
  123. data/test/full_2_3_5_app_with_tests/test/unit/helpers/gets_trollied_helper_test.rb +75 -0
  124. data/test/full_2_3_5_app_with_tests/test/unit/helpers/orders_helper_test.rb +34 -0
  125. data/test/full_2_3_5_app_with_tests/test/unit/line_item_test.rb +28 -0
  126. data/test/full_2_3_5_app_with_tests/test/unit/note_test.rb +14 -0
  127. data/test/full_2_3_5_app_with_tests/test/unit/order_test.rb +65 -0
  128. data/test/full_2_3_5_app_with_tests/test/unit/trolley_test.rb +80 -0
  129. data/test/full_2_3_5_app_with_tests/tmp/restart.txt +0 -0
  130. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/init.rb +1 -0
  131. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/base.rb +31 -0
  132. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/force_extension.rb +57 -0
  133. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/locale.rb +70 -0
  134. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/pagination.rb +33 -0
  135. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter/uuid_token.rb +78 -0
  136. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/lib/routing_filter.rb +94 -0
  137. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/force_extension_spec.rb +65 -0
  138. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/generation_spec.rb +367 -0
  139. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/pagination_extension_spec.rb +19 -0
  140. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/recognition_spec.rb +76 -0
  141. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/routing_filter_spec.rb +114 -0
  142. data/test/full_2_3_5_app_with_tests/vendor/plugins/routing-filter/spec/spec_helper.rb +108 -0
  143. data/test/helper.rb +10 -0
  144. metadata +345 -0
@@ -0,0 +1,75 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class GetsTrolliedHelperTest < ActionView::TestCase
5
+ include HasTrolleyControllerHelpers::UrlFor
6
+
7
+ context "Helpers for stuff that gets trollied" do
8
+ setup do
9
+ @item = Factory.create(:item, :label => "a label")
10
+
11
+ @form_fragment = "form method=\"post\" action=\"/items/1/line_items\""
12
+ @input_fragment = "input type=\"submit\" value=\"+ Add to cart\""
13
+
14
+ def current_user
15
+ @current_user ||= User.first || Factory.create(:user)
16
+ end
17
+ end
18
+
19
+ should "provide button_to_place_in_trolley that returns a form to add to cart for passed in purchasable_item" do
20
+ assert_has_button_in(button_to_place_in_trolley(@item))
21
+ end
22
+
23
+ should "provide in_trolley_status_or_button_to_place_in_trolley that returns a form to add to cart for passed in purchasable_item when the item hasn't been added yet" do
24
+ assert_has_button_in(in_trolley_status_or_button_to_place_in_trolley(@item))
25
+ end
26
+
27
+ context "should provide in_trolley_status_or_button_to_place_in_trolley that" do
28
+ setup do
29
+ @trolley = current_user.trolley
30
+ @trolley.add(@item)
31
+ end
32
+
33
+ should "return a statement that item is in user's trolley for passed in purchasable_item when has previously been added" do
34
+ value = in_trolley_status_or_button_to_place_in_trolley(@item)
35
+
36
+ assert value.include?("<div class=\"in-trolley\">" +
37
+ t('gets_trollied_helper.in_trolley') +
38
+ "</div>")
39
+
40
+ assert_does_not_have_button_in(value)
41
+ end
42
+
43
+ context "when passed in purchasable_item has previously been added, but order has been checked out" do
44
+ setup do
45
+ @order = @trolley.selected_order
46
+ @order.checkout!
47
+ end
48
+
49
+ should "return a statement that item is in user's trolley " do
50
+ value = in_trolley_status_or_button_to_place_in_trolley(@item)
51
+ assert_does_not_have_button_in(value)
52
+ end
53
+
54
+ should "return add to cart after the order has been moved to another state besides in_process" do
55
+ @order.cancel!
56
+ assert_has_button_in(in_trolley_status_or_button_to_place_in_trolley(@item))
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ private
64
+
65
+ def assert_has_button_in(value)
66
+ assert value.include?(@form_fragment) &&
67
+ value.include?(@input_fragment)
68
+ end
69
+
70
+ def assert_does_not_have_button_in(value)
71
+ assert !value.include?(@form_fragment) &&
72
+ !value.include?(@input_fragment)
73
+ end
74
+
75
+ end
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class OrdersHelperTest < ActionView::TestCase
5
+ context "Helpers for orders" do
6
+
7
+ should "respond to button_to event method for an order for all possible events" do
8
+ Order.workflow_event_names.each do |event_name|
9
+ assert respond_to?("button_to_#{event_name}")
10
+ end
11
+ end
12
+
13
+ should "have show_count_for method that takes a number and returns formatted string" do
14
+ assert_equal ' (2)', show_count_for(2)
15
+ end
16
+
17
+ should "have state_links that returns a list of links to order states" do
18
+ @order1 = Factory.create(:order)
19
+ @order1.checkout!
20
+
21
+ @order2 = Factory.create(:order)
22
+ @order2.checkout!
23
+ @order2.cancel!
24
+
25
+ @order3 = Factory.create(:order)
26
+
27
+ @state = 'in_process' # default
28
+
29
+ html = "<ul id=\"state-links\" class=\"horizontal-list\"><li class=\"state-link first\">#{I18n.t('orders.index.in_process')} (1)</li><li class=\"state-link\"><a href=\"/orders?state=cancelled\">#{I18n.t('orders.index.cancelled')} (1)</a></li><li class=\"state-link\"><a href=\"/orders?state=current\">#{I18n.t('orders.index.current')} (1)</a></li></ul>"
30
+
31
+ assert_equal html, state_links
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class LineItemTest < ActiveSupport::TestCase
5
+ context "A line_item" do
6
+ setup do
7
+ @item = Factory.create(:item)
8
+
9
+ @order = Factory.create(:order)
10
+
11
+ @order.add(@item)
12
+
13
+ @line_item = @order.line_items.first
14
+ end
15
+
16
+ should "have a user through its order's user" do
17
+ assert_equal @order.user, @line_item.user
18
+ end
19
+
20
+ should "have a description_for_purchasing method" do
21
+ assert_respond_to @line_item, :description_for_purchasing
22
+ end
23
+
24
+ should "return the purchasable_item's purchase_description as delegated" do
25
+ assert_equal @item.description_for_purchasing, @line_item.description_for_purchasing
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class NoteTest < ActiveSupport::TestCase
5
+ context "A note" do
6
+ setup do
7
+ @order = Factory.create(:order)
8
+ end
9
+
10
+ should "be able to be created for an order" do
11
+ assert_nothing_raised { @order.notes.create!(:body => "a test note", :user => User.first) }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,65 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class OrderTest < ActiveSupport::TestCase
5
+ context "An order" do
6
+ setup do
7
+ @item = Factory.create(:item)
8
+ @order = Factory.create(:order)
9
+ end
10
+
11
+ context "class" do
12
+
13
+ context "have a named scope 'in' that takes a state as an argument" do
14
+ should "return all orders if 'all' is the special case argument" do
15
+ assert_equal @order, Order.in('all').first
16
+ @order.checkout!
17
+ assert_equal @order, Order.in('all').first
18
+ end
19
+
20
+ should "return orders in that state" do
21
+ @order.checkout!
22
+ assert_equal @order, Order.in('in_process').first
23
+ end
24
+ end
25
+ end
26
+
27
+ should "have a user through its trolley's user" do
28
+ assert_equal @order.trolley.user, @order.user
29
+ end
30
+
31
+ # test that we have integration with the workflow gem
32
+ should "have a workflow_state and the default is current" do
33
+ assert_equal 'current', @order.workflow_state
34
+ end
35
+
36
+ should "have a current_state" do
37
+ assert @order.respond_to?(:current_state)
38
+ end
39
+
40
+ should "transition to in_process when checked out" do
41
+ @order.checkout!
42
+ # assert @order.ordered?
43
+ assert @order.in_process?
44
+ end
45
+
46
+ context "after adding an item to it" do
47
+ setup do
48
+ @order.add(@item)
49
+ end
50
+
51
+ should "create a line_item with the item" do
52
+ assert_purchasable_item_matches
53
+ end
54
+
55
+ should "return true when it contains an item" do
56
+ assert @order.contains?(@item)
57
+ end
58
+ end
59
+
60
+ should "return false when it does not contain an item" do
61
+ assert @order.contains?(@item) == false
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,80 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ class TrolleyTest < ActiveSupport::TestCase
5
+ context "A trolley" do
6
+ setup do
7
+ @item = Factory.create(:item)
8
+ @trolley = Factory.create(:trolley)
9
+ end
10
+
11
+ context "when having an item placed in it" do
12
+ setup do
13
+ @trolley.add(@item)
14
+ @order = @trolley.selected_order
15
+ end
16
+
17
+ should "result in an order with a line_item for that item" do
18
+ assert_purchasable_item_matches
19
+ end
20
+
21
+ should "answer true for contains? method" do
22
+ assert @trolley.contains?(@item)
23
+ end
24
+
25
+ should "return true for has_active_order_for? purchasable_item" do
26
+ assert @trolley.has_active_order_for?(@item)
27
+ end
28
+
29
+ context "and the item has already been placed in trolley, but first order has been checked out" do
30
+ setup do
31
+ @order.checkout!
32
+ end
33
+
34
+ should "answer false for contains? method when current state specified" do
35
+ assert @trolley.contains?(@item, :current) == false
36
+ end
37
+
38
+ should "answer true for contains? method when the item is in order with a given state" do
39
+ assert @trolley.contains?(@item, :in_process)
40
+ end
41
+
42
+ should "have correct_order be an order that is current, not in_process order" do
43
+ correct_order = @trolley.correct_order(@item)
44
+ assert correct_order != @order
45
+ assert correct_order.current?
46
+ end
47
+
48
+ should "return true for has_active_order_for? purchasable_item" do
49
+ assert @trolley.has_active_order_for?(@item)
50
+ end
51
+
52
+ context "and the order is then moved from in_process" do
53
+ should "return false for has_active_order_for? purchasable_item" do
54
+ @order.cancel!
55
+ assert !@trolley.has_active_order_for?(@item)
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
63
+ context "when an item is not in a trolley" do
64
+ should "answer false for contains? method" do
65
+ assert @trolley.contains?(@item) == false
66
+ end
67
+
68
+ should "answer false for contains? method even if there is an order for the trolley" do
69
+ @trolley.selected_order
70
+
71
+ assert @trolley.contains?(@item) == false
72
+ end
73
+
74
+ should "return false for has_active_order_for? purchasable_item" do
75
+ assert !@trolley.has_active_order_for?(@item)
76
+ end
77
+ end
78
+
79
+ end
80
+ end
File without changes
@@ -0,0 +1 @@
1
+ require 'routing_filter'
@@ -0,0 +1,31 @@
1
+ module RoutingFilter
2
+ class Base
3
+ class_inheritable_accessor :active
4
+ self.active = true
5
+
6
+ attr_accessor :chain, :options
7
+
8
+ def initialize(options = {})
9
+ @options = options
10
+ options.each { |name, value| instance_variable_set :"@#{name}", value }
11
+ end
12
+
13
+ def run(method, *args, &block)
14
+ _next = successor ? lambda { successor.run(method, *args, &block) } : block
15
+ active ? send(method, *args, &_next) : _next.call(*args)
16
+ end
17
+
18
+ def run_reverse(method, *args, &block)
19
+ _prev = predecessor ? lambda { predecessor.run(method, *args, &block) } : block
20
+ active ? send(method, *args, &_prev) : _prev.call(*args)
21
+ end
22
+
23
+ def predecessor
24
+ @chain.predecessor(self)
25
+ end
26
+
27
+ def successor
28
+ @chain.successor(self)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,57 @@
1
+ require 'routing_filter/base'
2
+
3
+ module RoutingFilter
4
+ class ForceExtension < Base
5
+ attr_reader :extension, :exclude
6
+
7
+ def initialize(*args)
8
+ super
9
+ @extension ||= 'html'
10
+ @exclude = %r(^(http.?://[^/]+)?\/?$) if @exclude.nil?
11
+ end
12
+
13
+ def around_recognize(path, env, &block)
14
+ extract_extension!(path) unless excluded?(path)
15
+ yield(path, env)
16
+ end
17
+
18
+ def around_generate(*args, &block)
19
+ returning yield do |result|
20
+ url = result.is_a?(Array) ? result.first : result
21
+ append_extension!(url) if append_extension?(url)
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ def extract_extension!(path)
28
+ path.sub! /\.#{extension}$/, ''
29
+ $1
30
+ end
31
+
32
+ def append_extension?(url)
33
+ !(url.blank? || excluded?(url) || mime_extension?(url))
34
+ end
35
+
36
+ def excluded?(url)
37
+ case exclude
38
+ when Regexp
39
+ url =~ exclude
40
+ when Proc
41
+ exclude.call(url)
42
+ end
43
+ end
44
+
45
+ def mime_extension?(url)
46
+ url =~ /\.#{Mime::EXTENSION_LOOKUP.keys.join('|')}(\?|$)/
47
+ end
48
+
49
+ def append_extension!(url)
50
+ url.replace url.sub(/(\?|$)/, ".#{extension}\\1")
51
+ end
52
+
53
+ def append_page!(url, page)
54
+ url.replace "#{url}/pages/#{page}"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,70 @@
1
+ require 'i18n'
2
+ require 'routing_filter/base'
3
+
4
+ module RoutingFilter
5
+ class Locale < Base
6
+ @@include_default_locale = true
7
+ cattr_writer :include_default_locale
8
+
9
+ class << self
10
+ def include_default_locale?
11
+ @@include_default_locale
12
+ end
13
+
14
+ def locales
15
+ @@locales ||= I18n.available_locales.map(&:to_sym)
16
+ end
17
+
18
+ def locales=(locales)
19
+ @@locales = locales.map(&:to_sym)
20
+ end
21
+
22
+ def locales_pattern
23
+ @@locales_pattern ||= %r(^/(#{self.locales.map { |l| Regexp.escape(l.to_s) }.join('|')})(?=/|$))
24
+ end
25
+ end
26
+
27
+ def around_recognize(path, env, &block)
28
+ locale = extract_locale!(path) # remove the locale from the beginning of the path
29
+ returning yield do |params| # invoke the given block (calls more filters and finally routing)
30
+ params[:locale] = locale if locale # set recognized locale to the resulting params hash
31
+ end
32
+ end
33
+
34
+ def around_generate(*args, &block)
35
+ locale = args.extract_options!.delete(:locale) # extract the passed :locale option
36
+ locale = I18n.locale if locale.nil? # default to I18n.locale when locale is nil (could also be false)
37
+ locale = nil unless valid_locale?(locale) # reset to no locale when locale is not valid
38
+
39
+ returning yield do |result|
40
+ if locale && prepend_locale?(locale)
41
+ url = result.is_a?(Array) ? result.first : result
42
+ prepend_locale!(url, locale)
43
+ end
44
+ end
45
+ end
46
+
47
+ protected
48
+
49
+ def extract_locale!(path)
50
+ path.sub! self.class.locales_pattern, ''
51
+ $1
52
+ end
53
+
54
+ def prepend_locale?(locale)
55
+ self.class.include_default_locale? || !default_locale?(locale)
56
+ end
57
+
58
+ def valid_locale?(locale)
59
+ locale && self.class.locales.include?(locale.to_sym)
60
+ end
61
+
62
+ def default_locale?(locale)
63
+ locale && locale.to_sym == I18n.default_locale.to_sym
64
+ end
65
+
66
+ def prepend_locale!(url, locale)
67
+ url.sub!(%r(^(http.?://[^/]*)?(.*))) { "#{$1}/#{locale}#{$2}" }
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,33 @@
1
+ require 'routing_filter/base'
2
+
3
+ module RoutingFilter
4
+ class Pagination < Base
5
+ def around_recognize(path, env, &block)
6
+ page = extract_page!(path)
7
+ returning yield(path, env) do |params|
8
+ params[:page] = page.to_i if page
9
+ end
10
+ end
11
+
12
+ def around_generate(*args, &block)
13
+ page = args.extract_options!.delete(:page)
14
+ returning yield do |result|
15
+ if page && page != 1
16
+ url = result.is_a?(Array) ? result.first : result
17
+ append_page!(url, page)
18
+ end
19
+ end
20
+ end
21
+
22
+ protected
23
+
24
+ def extract_page!(path)
25
+ path.sub! %r(/pages/([\d]+)/?$), ''
26
+ $1
27
+ end
28
+
29
+ def append_page!(url, page)
30
+ url.sub!(/($|\?)/) { "/pages/#{page}#{$1}" }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,78 @@
1
+ # This is a routing filter that extracts UUIDs from urls and exposes them via
2
+ # params[:uuid_token]
3
+ #
4
+ # URL scheme: http://example.com/d00fbbd1-82b6-4c1a-a57d-098d529d6854/your_route
5
+ #
6
+ # To enable this filter add map.filter :uuid_token to your routes.rb
7
+ #
8
+ # To make your restful path helpers use this :uuid_token use:
9
+ # new_post_path(:uuid_token => params[:uuid_token])
10
+ #
11
+ # If don't want to pass the :uuid_token manually you can add a custom module
12
+ # like this to your RAILS_ROOT/lib directory:
13
+ #
14
+ # module AccessToken
15
+ #
16
+ # class << self
17
+ # def current=(token)
18
+ # Thread.current[:uuid_token] = token
19
+ # end
20
+ #
21
+ # def current
22
+ # Thread.current[:uuid_token] ||= ""
23
+ # end
24
+ # end
25
+ #
26
+ # end
27
+ #
28
+ # Now in your application_controller you can set a before_filter which sets that
29
+ # token for every request:
30
+ #
31
+ # before_filter :set_token
32
+ #
33
+ # protected
34
+ #
35
+ # def set_token
36
+ # AccessToken.current = params[:uuid_token] if params[:uuid_token]
37
+ # end
38
+ #
39
+ # As you can see below in the around_generate method, if you don't provide a
40
+ # :uuid_token argument for your restful path helpers it will try to get the
41
+ # current :uuid_token from the AccessToken module.
42
+
43
+ require 'routing_filter/base'
44
+
45
+ module RoutingFilter
46
+ class UuidToken < Base
47
+
48
+ def around_recognize(path, env, &block)
49
+ token = extract_token!(path) # remove the token from the beginning of the path
50
+ returning yield do |params| # invoke the given block (calls more filters and finally routing)
51
+ params[:uuid_token] = token if token # set recognized token to the resulting params hash
52
+ end
53
+ end
54
+
55
+ def around_generate(*args, &block)
56
+ token = args.extract_options!.delete(:uuid_token) # extract the passed :token option
57
+ token = AccessToken.current if AccessToken && token.nil? # default to AccessToken.current when token is nil (could also be false)
58
+
59
+ returning yield do |result|
60
+ if token
61
+ url = result.is_a?(Array) ? result.first : result
62
+ prepend_token!(url, token)
63
+ end
64
+ end
65
+ end
66
+
67
+ protected
68
+
69
+ def extract_token!(path)
70
+ path.sub! /([a-z\d]{8}\-[a-z\d]{4}\-[a-z\d]{4}\-[a-z\d]{4}\-[a-z\d]{12})\//, ''
71
+ $1
72
+ end
73
+
74
+ def prepend_token!(url, token)
75
+ url.sub!(%r(^(http.?://[^/]*)?(.*))) { "#{$1}/#{token}#{$2}" }
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,94 @@
1
+ module RoutingFilter
2
+ mattr_accessor :active
3
+ @@active = true
4
+
5
+ class Chain < Array
6
+ def << (filter)
7
+ filter.chain = self
8
+ super
9
+ end
10
+
11
+ def run(method, *args, &final)
12
+ RoutingFilter.active && !first.nil? ? first.run(method, *args, &final) : final.call
13
+ end
14
+
15
+ def run_reverse(method, *args, &final)
16
+ RoutingFilter.active && !last.nil? ? last.run_reverse(method, *args, &final) : final.call
17
+ end
18
+
19
+ def predecessor(filter)
20
+ ix = index(filter)
21
+ ix > 0 ? self[ix - 1] : nil
22
+ end
23
+
24
+ def successor(filter)
25
+ ix = index(filter)
26
+ ix < length - 1 ? self[ix + 1] : nil
27
+ end
28
+ end
29
+ end
30
+
31
+ # allows to install a filter to the route set by calling: map.filter 'locale'
32
+ ActionController::Routing::RouteSet::Mapper.class_eval do
33
+ def filter(name, options = {})
34
+ require options.delete(:file) || "routing_filter/#{name}"
35
+ klass = RoutingFilter.const_get(name.to_s.camelize)
36
+ @set.filters << klass.new(options)
37
+ end
38
+ end
39
+
40
+ # same here for the optimized url generation in named routes
41
+ ActionController::Routing::RouteSet::NamedRouteCollection.class_eval do
42
+ # gosh. monkey engineering optimization code
43
+ def generate_optimisation_block_with_filtering(*args)
44
+ code = generate_optimisation_block_without_filtering(*args)
45
+ if match = code.match(%r(^return (.*) if (.*)))
46
+ # returned string must not contain newlines, or we'll spill out of inline code comments in
47
+ # ActionController::Routing::RouteSet::NamedRouteCollection#define_url_helper
48
+ "returning(#{match[1]}) { |result|" +
49
+ " ActionController::Routing::Routes.filters.run_reverse(:around_generate, *args, &lambda{ result }) " +
50
+ "} if #{match[2]}"
51
+ end
52
+ end
53
+ alias_method_chain :generate_optimisation_block, :filtering
54
+ end
55
+
56
+ ActionController::Routing::RouteSet.class_eval do
57
+ def clear_with_filtering!
58
+ @filters.clear if @filters
59
+ clear_without_filtering!
60
+ end
61
+ alias_method_chain :clear!, :filtering
62
+
63
+ attr_writer :filters
64
+
65
+ def filters
66
+ @filters ||= RoutingFilter::Chain.new
67
+ end
68
+
69
+ def recognize_path_with_filtering(path, env = {})
70
+ path = ::URI.unescape(path.dup) # string is frozen due to memoize
71
+ filters.run(:around_recognize, path, env, &lambda{ recognize_path_without_filtering(path, env) })
72
+ end
73
+ alias_method_chain :recognize_path, :filtering
74
+
75
+ def generate_with_filtering(*args)
76
+ filters.run_reverse(:around_generate, args.first, &lambda{ generate_without_filtering(*args) })
77
+ end
78
+ alias_method_chain :generate, :filtering
79
+
80
+ # add some useful information to the request environment
81
+ # right, this is from jamis buck's excellent article about routes internals
82
+ # http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2
83
+ # TODO move this ... where?
84
+ alias_method :extract_request_environment_without_host, :extract_request_environment unless method_defined? :extract_request_environment_without_host
85
+ def extract_request_environment(request)
86
+ returning extract_request_environment_without_host(request) do |env|
87
+ env.merge! :host => request.host,
88
+ :port => request.port,
89
+ :host_with_port => request.host_with_port,
90
+ :domain => request.domain,
91
+ :subdomain => request.subdomains.first
92
+ end
93
+ end
94
+ end