state_pattern 1.2.0 → 1.3.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 (93) hide show
  1. data/.gitignore +2 -0
  2. data/README.rdoc +188 -6
  3. data/Rakefile +0 -58
  4. data/examples/rails_2_3_8_button_example/README +243 -0
  5. data/examples/rails_2_3_8_button_example/Rakefile +10 -0
  6. data/examples/rails_2_3_8_button_example/app/controllers/application_controller.rb +2 -0
  7. data/examples/rails_2_3_8_button_example/app/controllers/buttons_controller.rb +18 -0
  8. data/examples/rails_2_3_8_button_example/app/helpers/application_helper.rb +3 -0
  9. data/examples/rails_2_3_8_button_example/app/models/button.rb +4 -0
  10. data/examples/rails_2_3_8_button_example/app/models/button/off.rb +7 -0
  11. data/examples/rails_2_3_8_button_example/app/models/button/on.rb +8 -0
  12. data/examples/rails_2_3_8_button_example/app/views/buttons/show.html.erb +9 -0
  13. data/examples/rails_2_3_8_button_example/app/views/layouts/application.html.erb +12 -0
  14. data/examples/rails_2_3_8_button_example/config/boot.rb +110 -0
  15. data/examples/rails_2_3_8_button_example/config/database.yml +22 -0
  16. data/examples/rails_2_3_8_button_example/config/environment.rb +41 -0
  17. data/examples/rails_2_3_8_button_example/config/environments/development.rb +17 -0
  18. data/examples/rails_2_3_8_button_example/config/environments/production.rb +28 -0
  19. data/examples/rails_2_3_8_button_example/config/environments/test.rb +28 -0
  20. data/examples/rails_2_3_8_button_example/config/initializers/backtrace_silencers.rb +7 -0
  21. data/examples/rails_2_3_8_button_example/config/initializers/cookie_verification_secret.rb +7 -0
  22. data/examples/rails_2_3_8_button_example/config/initializers/inflections.rb +10 -0
  23. data/examples/rails_2_3_8_button_example/config/initializers/mime_types.rb +5 -0
  24. data/examples/rails_2_3_8_button_example/config/initializers/new_rails_defaults.rb +21 -0
  25. data/examples/rails_2_3_8_button_example/config/initializers/session_store.rb +15 -0
  26. data/examples/rails_2_3_8_button_example/config/locales/en.yml +5 -0
  27. data/examples/rails_2_3_8_button_example/config/routes.rb +4 -0
  28. data/examples/rails_2_3_8_button_example/db/development.sqlite3 +0 -0
  29. data/examples/rails_2_3_8_button_example/db/migrate/20101009073737_create_buttons.rb +13 -0
  30. data/examples/rails_2_3_8_button_example/db/schema.rb +20 -0
  31. data/examples/rails_2_3_8_button_example/db/seeds.rb +7 -0
  32. data/examples/rails_2_3_8_button_example/db/test.sqlite3 +0 -0
  33. data/examples/rails_2_3_8_button_example/doc/README_FOR_APP +2 -0
  34. data/examples/rails_2_3_8_button_example/public/images/rails.png +0 -0
  35. data/examples/rails_2_3_8_button_example/public/javascripts/application.js +2 -0
  36. data/examples/rails_2_3_8_button_example/public/javascripts/controls.js +963 -0
  37. data/examples/rails_2_3_8_button_example/public/javascripts/dragdrop.js +973 -0
  38. data/examples/rails_2_3_8_button_example/public/javascripts/effects.js +1128 -0
  39. data/examples/rails_2_3_8_button_example/public/javascripts/prototype.js +4320 -0
  40. data/examples/rails_2_3_8_button_example/public/javascripts/rails.js +175 -0
  41. data/examples/rails_2_3_8_button_example/script/about +4 -0
  42. data/examples/rails_2_3_8_button_example/script/console +3 -0
  43. data/examples/rails_2_3_8_button_example/script/dbconsole +3 -0
  44. data/examples/rails_2_3_8_button_example/script/destroy +3 -0
  45. data/examples/rails_2_3_8_button_example/script/generate +3 -0
  46. data/examples/rails_2_3_8_button_example/script/performance/benchmarker +3 -0
  47. data/examples/rails_2_3_8_button_example/script/performance/profiler +3 -0
  48. data/examples/rails_2_3_8_button_example/script/plugin +3 -0
  49. data/examples/rails_2_3_8_button_example/script/runner +3 -0
  50. data/examples/rails_2_3_8_button_example/script/server +3 -0
  51. data/examples/rails_3_button_example/Gemfile +31 -0
  52. data/examples/rails_3_button_example/Gemfile.lock +75 -0
  53. data/examples/rails_3_button_example/Rakefile +7 -0
  54. data/examples/rails_3_button_example/app/controllers/application_controller.rb +3 -0
  55. data/examples/rails_3_button_example/app/controllers/buttons_controller.rb +13 -0
  56. data/examples/rails_3_button_example/app/models/button.rb +4 -0
  57. data/examples/rails_3_button_example/app/models/button/off.rb +7 -0
  58. data/examples/rails_3_button_example/app/models/button/on.rb +8 -0
  59. data/examples/rails_3_button_example/app/views/buttons/show.html.erb +9 -0
  60. data/examples/rails_3_button_example/app/views/layouts/application.html.erb +13 -0
  61. data/examples/rails_3_button_example/config.ru +4 -0
  62. data/examples/rails_3_button_example/config/application.rb +42 -0
  63. data/examples/rails_3_button_example/config/boot.rb +13 -0
  64. data/examples/rails_3_button_example/config/database.yml +22 -0
  65. data/examples/rails_3_button_example/config/environment.rb +5 -0
  66. data/examples/rails_3_button_example/config/environments/development.rb +26 -0
  67. data/examples/rails_3_button_example/config/initializers/backtrace_silencers.rb +7 -0
  68. data/examples/rails_3_button_example/config/initializers/inflections.rb +10 -0
  69. data/examples/rails_3_button_example/config/initializers/mime_types.rb +5 -0
  70. data/examples/rails_3_button_example/config/initializers/secret_token.rb +7 -0
  71. data/examples/rails_3_button_example/config/initializers/session_store.rb +8 -0
  72. data/examples/rails_3_button_example/config/locales/en.yml +5 -0
  73. data/examples/rails_3_button_example/config/routes.rb +5 -0
  74. data/examples/rails_3_button_example/db/development.sqlite3 +0 -0
  75. data/examples/rails_3_button_example/db/migrate/20101009052900_create_buttons.rb +13 -0
  76. data/examples/rails_3_button_example/db/schema.rb +21 -0
  77. data/examples/rails_3_button_example/db/test.sqlite3 +0 -0
  78. data/examples/rails_3_button_example/public/javascripts/application.js +2 -0
  79. data/examples/rails_3_button_example/public/javascripts/prototype.js +6001 -0
  80. data/examples/rails_3_button_example/public/javascripts/rails.js +175 -0
  81. data/examples/rails_3_button_example/public/stylesheets/scaffold.css +56 -0
  82. data/examples/rails_3_button_example/script/rails +6 -0
  83. data/examples/semaphore.rb +58 -0
  84. data/lib/state_pattern.rb +3 -1
  85. data/lib/state_pattern/active_record.rb +44 -0
  86. data/rails/init.rb +3 -0
  87. data/test/state_pattern/active_record/active_record_test.rb +71 -0
  88. data/test/state_pattern/active_record/schema.rb +7 -0
  89. data/test/state_pattern/active_record/test_helper.rb +25 -0
  90. data/test/test_helper.rb +1 -0
  91. metadata +204 -24
  92. data/VERSION +0 -1
  93. data/state_pattern.gemspec +0 -59
@@ -0,0 +1,175 @@
1
+ (function() {
2
+ // Technique from Juriy Zaytsev
3
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
+ function isEventSupported(eventName) {
5
+ var el = document.createElement('div');
6
+ eventName = 'on' + eventName;
7
+ var isSupported = (eventName in el);
8
+ if (!isSupported) {
9
+ el.setAttribute(eventName, 'return;');
10
+ isSupported = typeof el[eventName] == 'function';
11
+ }
12
+ el = null;
13
+ return isSupported;
14
+ }
15
+
16
+ function isForm(element) {
17
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
+ }
19
+
20
+ function isInput(element) {
21
+ if (Object.isElement(element)) {
22
+ var name = element.nodeName.toUpperCase()
23
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
+ }
25
+ else return false
26
+ }
27
+
28
+ var submitBubbles = isEventSupported('submit'),
29
+ changeBubbles = isEventSupported('change')
30
+
31
+ if (!submitBubbles || !changeBubbles) {
32
+ // augment the Event.Handler class to observe custom events when needed
33
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
+ function(init, element, eventName, selector, callback) {
35
+ init(element, eventName, selector, callback)
36
+ // is the handler being attached to an element that doesn't support this event?
37
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
+ // "submit" => "emulated:submit"
40
+ this.eventName = 'emulated:' + this.eventName
41
+ }
42
+ }
43
+ )
44
+ }
45
+
46
+ if (!submitBubbles) {
47
+ // discover forms on the page by observing focus events which always bubble
48
+ document.on('focusin', 'form', function(focusEvent, form) {
49
+ // special handler for the real "submit" event (one-time operation)
50
+ if (!form.retrieve('emulated:submit')) {
51
+ form.on('submit', function(submitEvent) {
52
+ var emulated = form.fire('emulated:submit', submitEvent, true)
53
+ // if custom event received preventDefault, cancel the real one too
54
+ if (emulated.returnValue === false) submitEvent.preventDefault()
55
+ })
56
+ form.store('emulated:submit', true)
57
+ }
58
+ })
59
+ }
60
+
61
+ if (!changeBubbles) {
62
+ // discover form inputs on the page
63
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
+ // special handler for real "change" events
65
+ if (!input.retrieve('emulated:change')) {
66
+ input.on('change', function(changeEvent) {
67
+ input.fire('emulated:change', changeEvent, true)
68
+ })
69
+ input.store('emulated:change', true)
70
+ }
71
+ })
72
+ }
73
+
74
+ function handleRemote(element) {
75
+ var method, url, params;
76
+
77
+ var event = element.fire("ajax:before");
78
+ if (event.stopped) return false;
79
+
80
+ if (element.tagName.toLowerCase() === 'form') {
81
+ method = element.readAttribute('method') || 'post';
82
+ url = element.readAttribute('action');
83
+ params = element.serialize();
84
+ } else {
85
+ method = element.readAttribute('data-method') || 'get';
86
+ url = element.readAttribute('href');
87
+ params = {};
88
+ }
89
+
90
+ new Ajax.Request(url, {
91
+ method: method,
92
+ parameters: params,
93
+ evalScripts: true,
94
+
95
+ onComplete: function(request) { element.fire("ajax:complete", request); },
96
+ onSuccess: function(request) { element.fire("ajax:success", request); },
97
+ onFailure: function(request) { element.fire("ajax:failure", request); }
98
+ });
99
+
100
+ element.fire("ajax:after");
101
+ }
102
+
103
+ function handleMethod(element) {
104
+ var method = element.readAttribute('data-method'),
105
+ url = element.readAttribute('href'),
106
+ csrf_param = $$('meta[name=csrf-param]')[0],
107
+ csrf_token = $$('meta[name=csrf-token]')[0];
108
+
109
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
+ element.parentNode.insert(form);
111
+
112
+ if (method !== 'post') {
113
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
+ form.insert(field);
115
+ }
116
+
117
+ if (csrf_param) {
118
+ var param = csrf_param.readAttribute('content'),
119
+ token = csrf_token.readAttribute('content'),
120
+ field = new Element('input', { type: 'hidden', name: param, value: token });
121
+ form.insert(field);
122
+ }
123
+
124
+ form.submit();
125
+ }
126
+
127
+
128
+ document.on("click", "*[data-confirm]", function(event, element) {
129
+ var message = element.readAttribute('data-confirm');
130
+ if (!confirm(message)) event.stop();
131
+ });
132
+
133
+ document.on("click", "a[data-remote]", function(event, element) {
134
+ if (event.stopped) return;
135
+ handleRemote(element);
136
+ event.stop();
137
+ });
138
+
139
+ document.on("click", "a[data-method]", function(event, element) {
140
+ if (event.stopped) return;
141
+ handleMethod(element);
142
+ event.stop();
143
+ });
144
+
145
+ document.on("submit", function(event) {
146
+ var element = event.findElement(),
147
+ message = element.readAttribute('data-confirm');
148
+ if (message && !confirm(message)) {
149
+ event.stop();
150
+ return false;
151
+ }
152
+
153
+ var inputs = element.select("input[type=submit][data-disable-with]");
154
+ inputs.each(function(input) {
155
+ input.disabled = true;
156
+ input.writeAttribute('data-original-value', input.value);
157
+ input.value = input.readAttribute('data-disable-with');
158
+ });
159
+
160
+ var element = event.findElement("form[data-remote]");
161
+ if (element) {
162
+ handleRemote(element);
163
+ event.stop();
164
+ }
165
+ });
166
+
167
+ document.on("ajax:after", "form", function(event, element) {
168
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
+ inputs.each(function(input) {
170
+ input.value = input.readAttribute('data-original-value');
171
+ input.removeAttribute('data-original-value');
172
+ input.disabled = false;
173
+ });
174
+ });
175
+ })();
@@ -0,0 +1,56 @@
1
+ body { background-color: #fff; color: #333; }
2
+
3
+ body, p, ol, ul, td {
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px;
7
+ }
8
+
9
+ pre {
10
+ background-color: #eee;
11
+ padding: 10px;
12
+ font-size: 11px;
13
+ }
14
+
15
+ a { color: #000; }
16
+ a:visited { color: #666; }
17
+ a:hover { color: #fff; background-color:#000; }
18
+
19
+ div.field, div.actions {
20
+ margin-bottom: 10px;
21
+ }
22
+
23
+ #notice {
24
+ color: green;
25
+ }
26
+
27
+ .field_with_errors {
28
+ padding: 2px;
29
+ background-color: red;
30
+ display: table;
31
+ }
32
+
33
+ #error_explanation {
34
+ width: 450px;
35
+ border: 2px solid red;
36
+ padding: 7px;
37
+ padding-bottom: 0;
38
+ margin-bottom: 20px;
39
+ background-color: #f0f0f0;
40
+ }
41
+
42
+ #error_explanation h2 {
43
+ text-align: left;
44
+ font-weight: bold;
45
+ padding: 5px 5px 5px 15px;
46
+ font-size: 12px;
47
+ margin: -7px;
48
+ margin-bottom: 0px;
49
+ background-color: #c00;
50
+ color: #fff;
51
+ }
52
+
53
+ #error_explanation ul li {
54
+ font-size: 12px;
55
+ list-style: square;
56
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,58 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'state_pattern'
3
+
4
+ def wait(seconds)
5
+ seconds.downto(1) do |i|
6
+ print " #{i}"
7
+ $stdout.flush
8
+ sleep 1
9
+ end
10
+ end
11
+
12
+ class Stop < StatePattern::State
13
+ def next
14
+ wait 3
15
+ transition_to(Go)
16
+ end
17
+
18
+ def color
19
+ "Red"
20
+ end
21
+ end
22
+
23
+ class Go < StatePattern::State
24
+ def next
25
+ wait 2
26
+ transition_to(Caution)
27
+ end
28
+
29
+ def color
30
+ "Green"
31
+ end
32
+ end
33
+
34
+ class Caution < StatePattern::State
35
+ def next
36
+ wait 1
37
+ transition_to(Stop)
38
+ end
39
+
40
+ def color
41
+ "Amber"
42
+ end
43
+ end
44
+
45
+ class TrafficSemaphore
46
+ include StatePattern
47
+ set_initial_state Stop
48
+ end
49
+
50
+ semaphore = TrafficSemaphore.new
51
+
52
+ loop do
53
+ print semaphore.color
54
+ semaphore.next
55
+ puts
56
+ end
57
+
58
+
@@ -79,7 +79,7 @@ module StatePattern
79
79
  return @current_state_instance if @current_state_instance.class == state_class
80
80
  @current_state_instance = state_class.new(self, @current_state_instance)
81
81
  end
82
-
82
+
83
83
  def current_state_instance
84
84
  set_state if @current_state_instance.nil?
85
85
  @current_state_instance
@@ -108,3 +108,5 @@ module StatePattern
108
108
  valid_transition_targets && valid_transition_targets.include?(to_state)
109
109
  end
110
110
  end
111
+
112
+ require 'state_pattern/active_record' if defined? ActiveRecord::Base
@@ -0,0 +1,44 @@
1
+ module StatePattern
2
+ module ActiveRecord
3
+ def self.included(base)
4
+ base.class_eval do
5
+ include StatePattern
6
+
7
+ after_initialize :set_state_from_db
8
+ def set_state_from_db
9
+ set_state(state_string_as_class)
10
+ end
11
+
12
+ #enable after_initialize callback
13
+ if Rails.version >= "3.0.0"
14
+ def self.after_initialize; end
15
+ else
16
+ def after_initialize; end
17
+ end
18
+
19
+ def set_state_with_active_record_attribute(state_class = nil)
20
+ set_state_without_active_record_attribute(state_class)
21
+ write_attribute(self.class.state_attribute, @current_state_instance.class.name)
22
+ end
23
+ alias_method_chain :set_state, :active_record_attribute
24
+
25
+ def state=(new_state_string)
26
+ set_state(state_string_as_class(new_state_string))
27
+ end
28
+
29
+ def state_string_as_class(state_string = @attributes[self.class.state_attribute])
30
+ state_string.camelize.constantize unless state_string.nil?
31
+ end
32
+
33
+ def self.state_attribute
34
+ @state_attribute ||= "state"
35
+ end
36
+
37
+ def self.set_state_attribute(state_attr)
38
+ @state_attribute = state_attr.to_s
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'vendor/state_pattern/lib'))
2
+ require 'state_pattern'
3
+
@@ -0,0 +1,71 @@
1
+ require 'state_pattern/active_record/test_helper'
2
+
3
+ class ARButton < ActiveRecord::Base
4
+ class On < StatePattern::State
5
+ def press
6
+ transition_to(Off)
7
+ "#{stateable.button_name} is off"
8
+ end
9
+ end
10
+
11
+ class Off < StatePattern::State
12
+ def press
13
+ transition_to(On)
14
+ "#{stateable.button_name} is on"
15
+ end
16
+ end
17
+
18
+ include StatePattern::ActiveRecord
19
+ set_initial_state Off
20
+ valid_transitions [On, :press] => Off, [Off, :press] => On
21
+
22
+ def button_name
23
+ "The button"
24
+ end
25
+ end
26
+
27
+ class ARButton2 < ARButton
28
+ set_state_attribute :state2
29
+ set_initial_state ARButton::Off
30
+ end
31
+
32
+ Expectations do
33
+ expect "ARButton::Off" do
34
+ ARButton.create.state
35
+ end
36
+
37
+ expect "The button is on" do
38
+ button = ARButton.create
39
+ button.press
40
+ end
41
+
42
+ expect "ARButton::On" do
43
+ button = ARButton.create
44
+ button.press
45
+ button.state
46
+ end
47
+
48
+ expect "The button is off" do
49
+ button = ARButton.create
50
+ button.state = "ARButton::On"
51
+ button.press
52
+ end
53
+
54
+ expect "The button is off" do
55
+ button = ARButton.create
56
+ button.press
57
+ button.press
58
+ end
59
+
60
+ expect "ARButton::On" do
61
+ ARButton.create(:state => "ARButton::On").state
62
+ end
63
+
64
+ expect nil do
65
+ ARButton2.create.state
66
+ end
67
+
68
+ expect "ARButton::Off" do
69
+ ARButton2.create.state2
70
+ end
71
+ end
@@ -0,0 +1,7 @@
1
+ ::ActiveRecord::Schema.define do
2
+ create_table "ar_buttons" do |t|
3
+ t.integer "button_id"
4
+ t.string "state"
5
+ t.string "state2"
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+ require 'active_record'
3
+
4
+ {
5
+ "nulldb" => "active_record/connection_adapters/nulldb_adapter",
6
+ "expectations" => "expectations"
7
+ }.each do |gem_name, require_arg|
8
+ begin
9
+ require require_arg
10
+ rescue LoadError
11
+ puts "You need to install the #{gem_name} gem to run the tests"
12
+ end
13
+ end
14
+
15
+ #Nulldb expects Rails.root to be defined
16
+ class Rails
17
+ def self.root
18
+ File.dirname(__FILE__)
19
+ end
20
+ end
21
+
22
+ ActiveRecord::Base.establish_connection :adapter => :nulldb, :schema => 'schema.rb'
23
+
24
+ require 'rails/init'
25
+ require 'state_pattern/active_record'