hobo 0.8.8 → 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +34 -0
- data/Rakefile +30 -24
- data/bin/hobo +30 -10
- data/doctest/hobo/hobo_helper.rdoctest +92 -0
- data/doctest/hobo/lifecycles.rdoctest +261 -0
- data/doctest/scopes.rdoctest +387 -0
- data/dryml_generators/rapid/forms.dryml.erb +3 -3
- data/dryml_generators/rapid/pages.dryml.erb +4 -4
- data/lib/active_record/viewhints_validations_interceptor.rb +1 -1
- data/lib/hobo.rb +1 -1
- data/lib/hobo/accessible_associations.rb +3 -3
- data/lib/hobo/authentication_support.rb +1 -1
- data/lib/hobo/dryml.rb +10 -0
- data/lib/hobo/dryml/taglib.rb +3 -5
- data/lib/hobo/hobo_helper.rb +3 -1
- data/lib/hobo/include_in_save.rb +1 -0
- data/lib/hobo/lifecycles/actions.rb +6 -2
- data/lib/hobo/model.rb +1 -1
- data/lib/hobo/model_controller.rb +34 -12
- data/lib/hobo/permissions.rb +1 -1
- data/lib/hobo/rapid_helper.rb +3 -0
- data/lib/hobo/scopes/association_proxy_extensions.rb +8 -2
- data/lib/hobo/scopes/automatic_scopes.rb +3 -3
- data/lib/hobo/user_controller.rb +2 -1
- data/rails_generators/hobo/hobo_generator.rb +1 -1
- data/rails_generators/hobo/templates/application.dryml +0 -2
- data/rails_generators/hobo_admin_site/hobo_admin_site_generator.rb +45 -0
- data/rails_generators/hobo_admin_site/templates/admin.css +2 -0
- data/rails_generators/hobo_admin_site/templates/application.dryml +1 -0
- data/rails_generators/hobo_admin_site/templates/controller.rb +13 -0
- data/rails_generators/hobo_admin_site/templates/site_taglib.dryml +32 -0
- data/rails_generators/hobo_admin_site/templates/users_index.dryml +5 -0
- data/rails_generators/hobo_front_controller/hobo_front_controller_generator.rb +7 -1
- data/rails_generators/hobo_front_controller/templates/index.dryml +16 -0
- data/rails_generators/hobo_rapid/hobo_rapid_generator.rb +31 -1
- data/rails_generators/hobo_rapid/templates/hobo-rapid.js +5 -3
- data/rails_generators/hobo_rapid/templates/lowpro.js +40 -21
- data/rails_generators/hobo_rapid/templates/themes/clean/public/images/101-3B5F87-ACD3E6.png +0 -0
- data/rails_generators/hobo_rapid/templates/themes/clean/public/images/30-3E547A-242E42.png +0 -0
- data/rails_generators/hobo_rapid/templates/themes/clean/public/images/30-DBE1E5-FCFEF5.png +0 -0
- data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +12 -4
- data/rails_generators/hobo_subsite/hobo_subsite_generator.rb +1 -1
- data/rails_generators/hobo_user_controller/hobo_user_controller_generator.rb +22 -0
- data/rails_generators/hobo_user_controller/templates/accept_invitation.dryml +5 -0
- data/rails_generators/hobo_user_controller/templates/controller.rb +22 -0
- data/rails_generators/hobo_user_model/hobo_user_model_generator.rb +17 -1
- data/rails_generators/hobo_user_model/templates/invite.erb +9 -0
- data/rails_generators/hobo_user_model/templates/mailer.rb +15 -0
- data/rails_generators/hobo_user_model/templates/model.rb +31 -4
- data/taglibs/rapid_core.dryml +25 -6
- data/taglibs/rapid_forms.dryml +65 -24
- data/taglibs/rapid_lifecycles.dryml +1 -1
- data/taglibs/rapid_navigation.dryml +2 -2
- data/taglibs/rapid_plus.dryml +4 -3
- metadata +151 -210
- data/Manifest +0 -155
- data/hobo.gemspec +0 -46
- data/rails_generators/hobo_rapid/templates/themes/clean/public/images/100-3B5F87-ACD3E6.png +0 -0
data/CHANGES.txt
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
=== Hobo 0.8.9 ===
|
2
|
+
|
3
|
+
Enhancements:
|
4
|
+
|
5
|
+
-
|
6
|
+
[precompile_taglibs](http://groups.google.com/group/hobousers/browse_thread/thread/29694e75f60c0870/6b05f75f2f7e91f5)
|
7
|
+
allows you to precompile taglibs during application startup rather
|
8
|
+
than on demand.
|
9
|
+
|
10
|
+
- `--invite-only` options added ti generator
|
11
|
+
|
12
|
+
Major bug fixes:
|
13
|
+
|
14
|
+
- [Bug
|
15
|
+
461](https://hobo.lighthouseapp.com/projects/8324-hobo/tickets/461-hobo-is-not-compatible-with-firefox-35):
|
16
|
+
Firefox 3.5 problems were caused by lowpro. For existing projects,
|
17
|
+
you will have to update your copy of [public/javascripts/lowpro.js](http://github.com/tablatom/hobo/raw/master/hobo/rails_generators/hobo_rapid/templates/lowpro.js)
|
18
|
+
|
19
|
+
- [Bug
|
20
|
+
477](http://groups.google.com/group/hobousers/browse_thread/thread/5a15288f9703a8a4/58a8dee62b237d29)
|
21
|
+
caused problems when the user submitted a form from the index page.
|
22
|
+
|
23
|
+
- "collection" was renamed to "collection-heading" in the Rapid
|
24
|
+
generated show-page.
|
25
|
+
|
26
|
+
- [Bug
|
27
|
+
473](https://hobo.lighthouseapp.com/projects/8324/tickets/473-use-timezonenow-instead-of-timenow#ticket-473-5):
|
28
|
+
Hobo now uses any time zone's configured for the application rather
|
29
|
+
than using the server's time zone.
|
30
|
+
|
31
|
+
Minor bug fixes and enhancements:
|
32
|
+
|
33
|
+
See the [github log](http://github.com/bryanlarsen/hobo/commits/v0.8.5)
|
34
|
+
|
1
35
|
=== Hobo 0.8.8 ===
|
2
36
|
|
3
37
|
Hobo 0.8.8 comes with some slight changes to the colour scheme for the
|
data/Rakefile
CHANGED
@@ -2,6 +2,13 @@ require 'rake'
|
|
2
2
|
require 'rake/rdoctask'
|
3
3
|
require 'rake/testtask'
|
4
4
|
|
5
|
+
require 'activerecord'
|
6
|
+
ActiveRecord::ActiveRecordError # hack for https://rails.lighthouseapp.com/projects/8994/tickets/2577-when-using-activerecordassociations-outside-of-rails-a-nameerror-is-thrown
|
7
|
+
$:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '/lib')
|
8
|
+
$:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '/../hobofields/lib')
|
9
|
+
$:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '/../hobosupport/lib')
|
10
|
+
require 'hobo'
|
11
|
+
|
5
12
|
desc "Default Task"
|
6
13
|
task :default => [ :test ]
|
7
14
|
|
@@ -35,28 +42,27 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
35
42
|
end
|
36
43
|
|
37
44
|
|
38
|
-
# --- Packaging and Rubyforge --- #
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
45
|
+
# --- Packaging and Rubyforge & gemcutter & github--- #
|
46
|
+
|
47
|
+
begin
|
48
|
+
require 'jeweler'
|
49
|
+
Jeweler::Tasks.new do |gemspec|
|
50
|
+
gemspec.version = Hobo::VERSION
|
51
|
+
gemspec.name = "hobo"
|
52
|
+
gemspec.email = "tom@tomlocke.com"
|
53
|
+
gemspec.summary = "The web app builder for Rails"
|
54
|
+
gemspec.homepage = "http://hobocentral.net/"
|
55
|
+
gemspec.authors = ["Tom Locke"]
|
56
|
+
gemspec.rubyforge_project = "hobo"
|
57
|
+
gemspec.add_dependency("rails", [">= 2.2.2"])
|
58
|
+
gemspec.add_dependency("mislav-will_paginate", [">= 2.2.1"])
|
59
|
+
gemspec.add_dependency("hobosupport", ["= #{Hobo::VERSION}"])
|
60
|
+
gemspec.add_dependency("hobofields", ["= #{Hobo::VERSION}"])
|
61
|
+
end
|
62
|
+
Jeweler::GemcutterTasks.new
|
63
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
64
|
+
rubyforge.doc_task = false
|
65
|
+
end
|
66
|
+
rescue LoadError
|
67
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
59
68
|
end
|
60
|
-
|
61
|
-
|
62
|
-
|
data/bin/hobo
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
|
+
require 'pathname'
|
5
|
+
require 'activesupport'
|
6
|
+
|
4
7
|
Signal.trap("INT") { puts; exit }
|
5
8
|
|
6
9
|
hobo_src = File.join(File.dirname(__FILE__), "../hobo_files/plugin")
|
@@ -14,6 +17,7 @@ Options:
|
|
14
17
|
-d | --database <database> # e.g. mysql, sqlite
|
15
18
|
-r | --rails <version> # rails version to use
|
16
19
|
-n | --no-rails # don't run 'rails'. Assumes app-path=='.'
|
20
|
+
--invite-only # add features for an invite-ony website (admin site, no signup)
|
17
21
|
"
|
18
22
|
|
19
23
|
|
@@ -36,11 +40,10 @@ def command(*s)
|
|
36
40
|
exit(1) unless ok
|
37
41
|
end
|
38
42
|
|
39
|
-
user_model
|
40
|
-
create_db
|
41
|
-
run_rails
|
42
|
-
|
43
|
-
puts ARGV
|
43
|
+
user_model = "user"
|
44
|
+
create_db = false
|
45
|
+
run_rails = true
|
46
|
+
invite_ony = ""
|
44
47
|
|
45
48
|
while true
|
46
49
|
case arg_name = ARGV.shift
|
@@ -50,13 +53,16 @@ while true
|
|
50
53
|
when "--db-create"
|
51
54
|
create_db = true
|
52
55
|
when "--hobo-src"
|
53
|
-
hobo_src =
|
56
|
+
hobo_src = ARGV.shift
|
57
|
+
hobo_src = "../" + hobo_src unless Pathname.new(hobo_src).absolute?
|
54
58
|
when "-d", "--database"
|
55
59
|
database_type = ARGV.shift
|
56
60
|
when "-r", "--rails"
|
57
61
|
rails_version = ARGV.shift
|
58
62
|
when "-n", "--no-rails"
|
59
63
|
run_rails = false
|
64
|
+
when "--invite-only"
|
65
|
+
invite_ony = "--invite-only"
|
60
66
|
when "--help"
|
61
67
|
puts USAGE
|
62
68
|
exit
|
@@ -100,16 +106,16 @@ Dir.chdir(app_path) do
|
|
100
106
|
command(gen, "hobo --add-gem --add-routes")
|
101
107
|
|
102
108
|
puts "\nInstalling Hobo Rapid and default theme...\n"
|
103
|
-
command("#{gen} hobo_rapid --import-tags")
|
109
|
+
command("#{gen} hobo_rapid --import-tags #{invite_ony}")
|
104
110
|
|
105
111
|
if user_model
|
106
112
|
puts "\nCreating #{user_model} model and controller...\n"
|
107
|
-
command("#{gen} hobo_user_model #{user_model}")
|
108
|
-
command("#{gen} hobo_user_controller #{user_model}")
|
113
|
+
command("#{gen} hobo_user_model #{user_model} #{invite_ony}")
|
114
|
+
command("#{gen} hobo_user_controller #{user_model} #{invite_ony}")
|
109
115
|
end
|
110
116
|
|
111
117
|
puts "\nCreating standard pages...\n"
|
112
|
-
command("#{gen} hobo_front_controller front --delete-index --add-routes")
|
118
|
+
command("#{gen} hobo_front_controller front --delete-index --add-routes #{invite_ony}")
|
113
119
|
|
114
120
|
if create_db
|
115
121
|
puts "\nCreating databases"
|
@@ -117,3 +123,17 @@ Dir.chdir(app_path) do
|
|
117
123
|
end
|
118
124
|
end
|
119
125
|
|
126
|
+
if invite_ony.present?
|
127
|
+
puts %(
|
128
|
+
Invite-only website
|
129
|
+
If you wish to prevent all access to the site to non-members, add 'before_filter :login_required'
|
130
|
+
to the relevant controllers, e.g. to prevent all access to the site, add
|
131
|
+
|
132
|
+
include Hobo::AuthenticationSupport
|
133
|
+
before_filter :login_required
|
134
|
+
|
135
|
+
to application_controller.rb (note that the include statement is not required for hobo_controllers)
|
136
|
+
|
137
|
+
NOTE: You might want to sign up as the administrator before adding this!
|
138
|
+
)
|
139
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# Hobo::HoboHelper
|
2
|
+
|
3
|
+
Various view helpers
|
4
|
+
|
5
|
+
doctest_require: 'rubygems'
|
6
|
+
doctest_require: 'active_support'
|
7
|
+
doctest_require: 'mocha'
|
8
|
+
doctest_require: '../../../hobosupport/lib/hobosupport'
|
9
|
+
doctest_require: '../../../hobofields/lib/hobofields'
|
10
|
+
doctest_require: '../../lib/hobo'
|
11
|
+
|
12
|
+
Create a mock view layer:
|
13
|
+
|
14
|
+
>>
|
15
|
+
class View
|
16
|
+
extend Hobo::HoboHelper
|
17
|
+
class << self
|
18
|
+
protected_instance_methods.each {|m| public m }
|
19
|
+
|
20
|
+
def params; {} ;end
|
21
|
+
def subsite; "" ;end
|
22
|
+
def base_url; "" ;end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
>> RAILS_ROOT = "test-app"
|
26
|
+
|
27
|
+
Useful stuff
|
28
|
+
|
29
|
+
>> def init_mocha; $stubba = Mocha::Central.new; end
|
30
|
+
>>
|
31
|
+
class Thing
|
32
|
+
class Mocks; extend Mocha::AutoVerify; end
|
33
|
+
def self.mock(hash)
|
34
|
+
Mocks.mock(hash.update(:class => self))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
## `object_url`
|
40
|
+
|
41
|
+
Returns a canonical restful URL for a given object. THe Hobo routing is checked and URLs are only returned for routes that exist.
|
42
|
+
|
43
|
+
Note that `object_url` doesn't perform "reverse routing". It knows nothing about attractive URLs you may have declared in your routes file.
|
44
|
+
|
45
|
+
Something to link to:
|
46
|
+
|
47
|
+
>> init_mocha
|
48
|
+
>> thing = Thing.mock(:to_url_path => "things/1")
|
49
|
+
|
50
|
+
### Simple 'show' URLs
|
51
|
+
|
52
|
+
>> Hobo::ModelRouter.expects(:linkable?).with(Thing, :show, {:subsite => ''}).returns(true)
|
53
|
+
>> View.object_url(thing)
|
54
|
+
=> "/things/1"
|
55
|
+
|
56
|
+
Returns nil if ModelRouter says it's not linkable
|
57
|
+
|
58
|
+
>> Hobo::ModelRouter.expects(:linkable?).with(Thing, :show, {:subsite => ''}).returns(false)
|
59
|
+
>> View.object_url(thing)
|
60
|
+
=> nil
|
61
|
+
|
62
|
+
A URL to the 'edit' page:
|
63
|
+
|
64
|
+
>> Hobo::ModelRouter.expects(:linkable?).with(Thing, :edit, {:subsite => ''}).returns(true)
|
65
|
+
>> View.object_url(thing, :edit)
|
66
|
+
=> "/things/1/edit"
|
67
|
+
|
68
|
+
|
69
|
+
### POST URLs for creating new items in collections:
|
70
|
+
|
71
|
+
>> collection = mock(:origin => thing, :origin_attribute => "parts")
|
72
|
+
>> Hobo::ModelRouter.expects(:linkable?).with(Thing, :create_part, {:subsite => '', :method => :post}).returns(true)
|
73
|
+
>> View.object_url(collection, :method => :post)
|
74
|
+
=> "/things/1/parts"
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
|
@@ -0,0 +1,261 @@
|
|
1
|
+
# Hobo Lifecycles
|
2
|
+
|
3
|
+
In the REST style, which is popular with Rails coders, we view our objects a bit like documents: you can post them to a website, get them again later, make changes to them and delete them. Of course, these objects also have behaviour, which we typically implement by hooking functionality to the create / update / delete events (e.g. using callbacks such as `after_create` in ActiveRecord).
|
4
|
+
|
5
|
+
This works great for many situations, but some objects are *not* best thought of as documents that we create and edit. In particular, web apps often contain objects that model some kind of *process*. A good example is *friendship* in a social app. Here's a description of how friendship might work:
|
6
|
+
|
7
|
+
* Any user can **request** friendship with another user
|
8
|
+
* The other user can **accept** or **reject** (or perhaps **ignore**) the request.
|
9
|
+
* The friendship is only **active** once it's been accepted
|
10
|
+
* An active friendship can be **cancelled** by either user.
|
11
|
+
|
12
|
+
Not a create, update or delete in sight. Those bold words capture the way we think about the friendship much better. Of course we *could* implement friendship in a RESTful style, but we'd be doing just that -- *implementing* it, not *declaring* it. The life-cycle of the friendship would be hidden in our code, scattered across a bunch of callbacks, permission methods and state variables. Experience has shown this type of code to be tedious to write, *extremely* error prone and fragile when changing.
|
13
|
+
|
14
|
+
Hobo lifecycles is a mechanism for declaring the life-cycle of a model in a natural manner. It's a bit like `acts_as_state_machine`, but Hobo-style :-)
|
15
|
+
|
16
|
+
First the junk to get us started:
|
17
|
+
|
18
|
+
doctest_require: 'rubygems'
|
19
|
+
doctest_require: 'activerecord'
|
20
|
+
>> $:.unshift '/home/blarsen/dev/agility-master/vendor/plugins/hobo/hobo/lib'
|
21
|
+
>> $:.unshift '/home/blarsen/dev/agility-master/vendor/plugins/hobo/hobo/lib'
|
22
|
+
>> $:.unshift '/home/blarsen/dev/agility-master/vendor/plugins/hobo/hobofields/lib'
|
23
|
+
>> $:.unshift '/home/blarsen/dev/agility-master/vendor/plugins/hobo/hobosupport/lib'
|
24
|
+
>> require 'hobo'
|
25
|
+
>> require 'hobo/model'
|
26
|
+
>> ActiveRecord::Base.establish_connection(:adapter => "sqlite3",
|
27
|
+
:database => "lifecycle_doctest")
|
28
|
+
|
29
|
+
|
30
|
+
A user model for our example:
|
31
|
+
|
32
|
+
>>
|
33
|
+
class User < ActiveRecord::Base
|
34
|
+
hobo_model
|
35
|
+
fields do
|
36
|
+
name :string
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Now the friendship. For now we'll just declare the *invite* part of the lifecycle. We first declare the *states* -- there's only one for now. We then declare the *invite* action. This is the action that first creates the friendship, so we declare it with `create`:
|
41
|
+
|
42
|
+
>>
|
43
|
+
class Friendship < ActiveRecord::Base
|
44
|
+
hobo_model
|
45
|
+
belongs_to :requester, :class_name => "User"
|
46
|
+
belongs_to :requestee, :class_name => "User"
|
47
|
+
|
48
|
+
lifecycle do
|
49
|
+
state :requested
|
50
|
+
create :request, :params => [ :requestee ], :become => :requested, :user_becomes => :requester
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
Now let's get the DB ready:
|
55
|
+
|
56
|
+
doctest_require: '../../../hobofields/lib/hobo_fields/migration_generator'
|
57
|
+
>> up, down = HoboFields::MigrationGenerator.run
|
58
|
+
>> ActiveRecord::Migration.class_eval up
|
59
|
+
>> User.delete_all
|
60
|
+
>> Friendship.delete_all
|
61
|
+
|
62
|
+
We need some users to be friends:
|
63
|
+
|
64
|
+
>> tom = User.create(:name => "Tom")
|
65
|
+
>> bob = User.create(:name => "Bob")
|
66
|
+
|
67
|
+
Tom is allowed to request friendship:
|
68
|
+
|
69
|
+
>> Friendship::Lifecycle.can_request?(tom)
|
70
|
+
=> true
|
71
|
+
|
72
|
+
Tom does so:
|
73
|
+
|
74
|
+
>> f = Friendship::Lifecycle.request(tom, :requestee => bob)
|
75
|
+
>> f.requester.name
|
76
|
+
=> "Tom"
|
77
|
+
>> f.requestee.name
|
78
|
+
=> "Bob"
|
79
|
+
>> f.state
|
80
|
+
=> "requested"
|
81
|
+
|
82
|
+
To continue modeling the friendship lifecycle, we add some *transitions*:
|
83
|
+
|
84
|
+
>>
|
85
|
+
class Friendship
|
86
|
+
lifecycle do
|
87
|
+
state :active
|
88
|
+
transition :accept, { :requested => :active}, :available_to => :requestee
|
89
|
+
transition :reject, { :requested => :destroy}, :available_to => :requestee
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
Note:
|
94
|
+
|
95
|
+
* Part of the transition declaration is *who* that transition is for. These two were only for the `requestee`:
|
96
|
+
|
97
|
+
* `:destroy` is a special pseudo state: entering this state causes the record to be destroyed
|
98
|
+
|
99
|
+
We can test which transitions are available:
|
100
|
+
|
101
|
+
>> f.lifecycle.available_transitions_for(tom).*.name
|
102
|
+
=> []
|
103
|
+
>> f.lifecycle.available_transitions_for(bob).*.name
|
104
|
+
=> ["accept", "reject"]
|
105
|
+
>> f.lifecycle.can_accept?(tom)
|
106
|
+
=> false
|
107
|
+
>> f.lifecycle.can_reject?(tom)
|
108
|
+
=> false
|
109
|
+
>> f.lifecycle.can_accept?(bob)
|
110
|
+
=> true
|
111
|
+
>> f.lifecycle.can_reject?(bob)
|
112
|
+
=> true
|
113
|
+
|
114
|
+
Accept the friendship
|
115
|
+
|
116
|
+
>> f.lifecycle.accept(bob)
|
117
|
+
>> f.state
|
118
|
+
=> "active"
|
119
|
+
|
120
|
+
And now there's nowhere to go:
|
121
|
+
|
122
|
+
>> f.lifecycle.available_transitions_for(tom).*.name
|
123
|
+
=> []
|
124
|
+
>> f.lifecycle.available_transitions_for(bob).*.name
|
125
|
+
=> []
|
126
|
+
|
127
|
+
Cleanup
|
128
|
+
|
129
|
+
>> Friendship.delete_all
|
130
|
+
|
131
|
+
Let's try a rejected friendship:
|
132
|
+
|
133
|
+
>> f = Friendship::Lifecycle.request(tom, :requestee => bob)
|
134
|
+
>> f.state
|
135
|
+
=> "requested"
|
136
|
+
>> f.lifecycle.can_reject?(bob)
|
137
|
+
=> true
|
138
|
+
>> Friendship.count
|
139
|
+
=> 1
|
140
|
+
>> f.lifecycle.reject(bob)
|
141
|
+
>> Friendship.count
|
142
|
+
=> 0
|
143
|
+
|
144
|
+
Cleanup
|
145
|
+
|
146
|
+
>> User.delete_all
|
147
|
+
>> Friendship.delete_all
|
148
|
+
>> Friendship::Lifecycle.reset
|
149
|
+
|
150
|
+
## A bigger example
|
151
|
+
|
152
|
+
We'll run through the same example again, but we'll add some features
|
153
|
+
|
154
|
+
Transitions and states can have actions associated with them. A common use might be to send an email. We'll simulate that with a global variable `$emails`
|
155
|
+
|
156
|
+
>> $emails = []
|
157
|
+
|
158
|
+
We'll extend the lifecycle to allow:
|
159
|
+
|
160
|
+
* the requester to back out of the request
|
161
|
+
|
162
|
+
* the requestee to ignore the request
|
163
|
+
|
164
|
+
* either party to cancel the active friendship
|
165
|
+
|
166
|
+
Here is the entire lifecycle
|
167
|
+
|
168
|
+
>>
|
169
|
+
class Friendship < ActiveRecord::Base
|
170
|
+
hobo_model
|
171
|
+
belongs_to :requester, :class_name => "User"
|
172
|
+
belongs_to :requestee, :class_name => "User"
|
173
|
+
|
174
|
+
lifecycle do
|
175
|
+
state :requested, :active, :ignored
|
176
|
+
|
177
|
+
create :requester, :request, :params => [ :requestee ], :become => :requested do
|
178
|
+
$emails << "Dear #{requestee.name}, #{requester.name} wants to be friends with you"
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
transition :requestee, :accept, { :requested => :active } do
|
183
|
+
$emails << "Dear #{requester.name}, #{requestee.name} is now your friend :-)"
|
184
|
+
end
|
185
|
+
|
186
|
+
transition :requestee, :reject, { :requested => :destroy } do
|
187
|
+
$emails << "Dear #{requester.name}, #{requestee.name} blew you out :-("
|
188
|
+
end
|
189
|
+
|
190
|
+
transition :requestee, :ignore, { :requested => :ignored }
|
191
|
+
|
192
|
+
transition :requester, :retract, { :requested => :destroy } do
|
193
|
+
$emails << "Dear #{requestee.name}, #{requester.name} reconsidered"
|
194
|
+
end
|
195
|
+
|
196
|
+
transition [ :requester, :requestee ], :cancel, { :active => :destroy }
|
197
|
+
# TODO: send the email - for this we need the acting user to be passed to the block
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
Check the simple accept still works, and sends emails
|
204
|
+
|
205
|
+
>> f = Friendship::Lifecycle.request(tom, :requestee => bob)
|
206
|
+
>> $emails.last
|
207
|
+
=> "Dear Bob, Tom wants to be friends with you"
|
208
|
+
>> f.lifecycle.accept(bob)
|
209
|
+
>> $emails.last
|
210
|
+
=> "Dear Tom, Bob is now your friend :-)"
|
211
|
+
>> f.lifecycle.active?
|
212
|
+
=> true
|
213
|
+
|
214
|
+
Rejection:
|
215
|
+
|
216
|
+
>> f = Friendship::Lifecycle.request(tom, :requestee => bob)
|
217
|
+
>> f.lifecycle.reject(bob)
|
218
|
+
>> $emails.last
|
219
|
+
=> "Dear Tom, Bob blew you out :-("
|
220
|
+
>> f.state
|
221
|
+
=> "destroy"
|
222
|
+
|
223
|
+
Retraction:
|
224
|
+
|
225
|
+
>> f = Friendship::Lifecycle.request(tom, :requestee => bob)
|
226
|
+
>> f.lifecycle.can_retract?(bob)
|
227
|
+
=> false
|
228
|
+
>> f.lifecycle.retract(tom)
|
229
|
+
>> $emails.last
|
230
|
+
=> "Dear Bob, Tom reconsidered"
|
231
|
+
>> f.lifecycle.active?
|
232
|
+
|
233
|
+
Ignoring
|
234
|
+
|
235
|
+
>> f = Friendship::Lifecycle.request(tom, :requestee => bob)
|
236
|
+
>> $emails = []
|
237
|
+
>> f.lifecycle.ignore(bob)
|
238
|
+
>> $emails # Ignoring shouldn't send any email
|
239
|
+
=> []
|
240
|
+
>> f.state
|
241
|
+
=> "ignored"
|
242
|
+
|
243
|
+
Requester cancels
|
244
|
+
|
245
|
+
>> f = Friendship::Lifecycle.request(tom, :requestee => bob)
|
246
|
+
>> f.lifecycle.can_cancel?(tom)
|
247
|
+
=> false
|
248
|
+
>> f.lifecycle.accept(bob)
|
249
|
+
>> f.lifecycle.cancel(tom)
|
250
|
+
>> f.state
|
251
|
+
=> "destroy"
|
252
|
+
|
253
|
+
|
254
|
+
|
255
|
+
|
256
|
+
|
257
|
+
|
258
|
+
|
259
|
+
|
260
|
+
|
261
|
+
|