hobo 0.8.8 → 0.8.9
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.
- 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
|
+
|