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
@@ -10,6 +10,22 @@
|
|
10
10
|
<ul>
|
11
11
|
<li>To customise this page: edit app/views/front/index.dryml</li>
|
12
12
|
</ul>
|
13
|
+
|
14
|
+
<%% if User.count == 0 -%>
|
15
|
+
<h3 style="margin-top: 20px;">There are no user accounts - please provide the details of the site administrator</h3>
|
16
|
+
<% if invite_only? %>
|
17
|
+
<form with="&this || User.new" without-cancel>
|
18
|
+
<field-list: fields="name, email_address, password, password_confirmation"/>
|
19
|
+
<submit: label="Register Administrator"/>
|
20
|
+
</form>
|
21
|
+
<% else -%>
|
22
|
+
<do with="&User.new"><%% this.exempt_from_edit_checks = true %>
|
23
|
+
<signup-form/>
|
24
|
+
</do>
|
25
|
+
<% end -%>
|
26
|
+
<%% end -%>
|
27
|
+
|
28
|
+
|
13
29
|
</section>
|
14
30
|
</header>
|
15
31
|
|
@@ -19,6 +19,12 @@ class HoboRapidGenerator < Hobo::Generator
|
|
19
19
|
m.file "hobo-rapid.css", "public/stylesheets/hobo-rapid.css"
|
20
20
|
create_all(m, "themes/clean/public", "public/hobothemes/clean")
|
21
21
|
create_all(m, "themes/clean/views", "app/views/taglibs/themes/clean")
|
22
|
+
|
23
|
+
if with_admin_site?
|
24
|
+
options = ["--make-front-site", "admin"]
|
25
|
+
options.unshift "--invite-only" if invite_only?
|
26
|
+
m.dependency 'hobo_admin_site', options
|
27
|
+
end
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
@@ -34,6 +40,18 @@ class HoboRapidGenerator < Hobo::Generator
|
|
34
40
|
<set-theme name="clean"/>
|
35
41
|
)
|
36
42
|
|
43
|
+
if with_admin_site?
|
44
|
+
tag += %(
|
45
|
+
<extend tag="page">
|
46
|
+
<old-page merge>
|
47
|
+
<footer:>
|
48
|
+
<a href="&admin_users_url" if="¤t_user.administrator?">Admin</a>
|
49
|
+
</footer:>
|
50
|
+
</old-page>
|
51
|
+
</extend>
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
37
55
|
src = File.read(path)
|
38
56
|
return if src.include?(tag)
|
39
57
|
|
@@ -47,9 +65,17 @@ class HoboRapidGenerator < Hobo::Generator
|
|
47
65
|
end
|
48
66
|
|
49
67
|
|
68
|
+
def with_admin_site?
|
69
|
+
options[:admin]
|
70
|
+
end
|
71
|
+
|
72
|
+
def invite_only?
|
73
|
+
options[:admin] == :invite_only
|
74
|
+
end
|
75
|
+
|
50
76
|
protected
|
51
77
|
def banner
|
52
|
-
"Usage: #{$0} #{spec.name} [--import-tags]"
|
78
|
+
"Usage: #{$0} #{spec.name} [--import-tags] [--admin | --invite-only]"
|
53
79
|
end
|
54
80
|
|
55
81
|
def add_options!(opt)
|
@@ -59,5 +85,9 @@ class HoboRapidGenerator < Hobo::Generator
|
|
59
85
|
"Modify taglibs/application.dryml to import hobo-rapid and theme tags ") do |v|
|
60
86
|
options[:import_tags] = true
|
61
87
|
end
|
88
|
+
opt.on("--admin",
|
89
|
+
"Generate an admin subsite") { |v| options[:admin] = true }
|
90
|
+
opt.on("--invite-only",
|
91
|
+
"Generate an admin subsite with features for an invite only app") { |v| options[:admin] = :invite_only }
|
62
92
|
end
|
63
93
|
end
|
@@ -628,9 +628,11 @@ new HoboBehavior("ul.input-many", {
|
|
628
628
|
|
629
629
|
this.element.selectChildren('li').each(function(li, index) {
|
630
630
|
li.select('*[name]').each(function(control) {
|
631
|
-
|
632
|
-
|
633
|
-
|
631
|
+
if(control.name) {
|
632
|
+
var changeId = control.id == control.name;
|
633
|
+
control.name = control.name.sub(new RegExp("^" + RegExp.escape(prefix) + "\[[0-9]+\]"), prefix + '[' + index +']');
|
634
|
+
if (changeId) control.id = control.name;
|
635
|
+
}
|
634
636
|
})
|
635
637
|
})
|
636
638
|
}
|
@@ -2,7 +2,7 @@ LowPro = {};
|
|
2
2
|
LowPro.Version = '0.5';
|
3
3
|
LowPro.CompatibleWithPrototype = '1.6';
|
4
4
|
|
5
|
-
if (Prototype.Version.indexOf(LowPro.CompatibleWithPrototype) != 0 && console && console.warn)
|
5
|
+
if (Prototype.Version.indexOf(LowPro.CompatibleWithPrototype) != 0 && window.console && window.console.warn)
|
6
6
|
console.warn("This version of Low Pro is tested with Prototype " + LowPro.CompatibleWithPrototype +
|
7
7
|
" it may not work as expected with this version (" + Prototype.Version + ")");
|
8
8
|
|
@@ -15,19 +15,19 @@ DOM = {};
|
|
15
15
|
// DOMBuilder for prototype
|
16
16
|
DOM.Builder = {
|
17
17
|
tagFunc : function(tag) {
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
18
|
+
return function() {
|
19
|
+
var attrs, children;
|
20
|
+
if (arguments.length>0) {
|
21
|
+
if (arguments[0].constructor == Object) {
|
22
|
+
attrs = arguments[0];
|
23
|
+
children = Array.prototype.slice.call(arguments, 1);
|
24
|
+
} else {
|
25
|
+
children = arguments;
|
26
|
+
};
|
27
|
+
children = $A(children).flatten()
|
28
|
+
}
|
29
|
+
return DOM.Builder.create(tag, attrs, children);
|
30
|
+
};
|
31
31
|
},
|
32
32
|
create : function(tag, attrs, children) {
|
33
33
|
attrs = attrs || {}; children = children || []; tag = tag.toLowerCase();
|
@@ -109,6 +109,14 @@ Event.addBehavior = function(rules) {
|
|
109
109
|
|
110
110
|
};
|
111
111
|
|
112
|
+
Event.delegate = function(rules) {
|
113
|
+
return function(e) {
|
114
|
+
var element = $(e.element());
|
115
|
+
for (var selector in rules)
|
116
|
+
if (element.match(selector)) return rules[selector].apply(this, $A(arguments));
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
112
120
|
Object.extend(Event.addBehavior, {
|
113
121
|
rules : {}, cache : [],
|
114
122
|
reassignAfterAjax : false,
|
@@ -122,9 +130,9 @@ Object.extend(Event.addBehavior, {
|
|
122
130
|
var match = sel.match(/^([^:]*)(?::(.*)$)?/), css = match[1], event = match[2];
|
123
131
|
$$(css).each(function(element) {
|
124
132
|
if (event) {
|
125
|
-
|
126
|
-
$(element).observe(event,
|
127
|
-
Event.addBehavior.cache.push([element, event,
|
133
|
+
var wrappedObserver = Event.addBehavior._wrapObserver(observer);
|
134
|
+
$(element).observe(event, wrappedObserver);
|
135
|
+
Event.addBehavior.cache.push([element, event, wrappedObserver]);
|
128
136
|
} else {
|
129
137
|
if (!element.$$assigned || !element.$$assigned.include(observer)) {
|
130
138
|
if (observer.attach) observer.attach(element);
|
@@ -198,7 +206,6 @@ var Behavior = {
|
|
198
206
|
parent = properties.shift();
|
199
207
|
|
200
208
|
var behavior = function() {
|
201
|
-
var behavior = arguments.callee;
|
202
209
|
if (!this.initialize) {
|
203
210
|
var args = $A(arguments);
|
204
211
|
|
@@ -245,13 +252,17 @@ var Behavior = {
|
|
245
252
|
return new this(element, Array.prototype.slice.call(arguments, 1));
|
246
253
|
},
|
247
254
|
_bindEvents : function(bound) {
|
248
|
-
for (var member in bound)
|
249
|
-
|
250
|
-
|
255
|
+
for (var member in bound) {
|
256
|
+
var matches = member.match(/^on(.+)/);
|
257
|
+
if (matches && typeof bound[member] == 'function')
|
258
|
+
bound.element.observe(matches[1], Event.addBehavior._wrapObserver(bound[member].bindAsEventListener(bound)));
|
259
|
+
}
|
251
260
|
}
|
252
261
|
}
|
253
262
|
};
|
254
263
|
|
264
|
+
|
265
|
+
|
255
266
|
Remote = Behavior.create({
|
256
267
|
initialize: function(options) {
|
257
268
|
if (this.element.nodeName == 'FORM') new Remote.Form(this.element, options);
|
@@ -264,11 +275,19 @@ Remote.Base = {
|
|
264
275
|
this.options = Object.extend({
|
265
276
|
evaluateScripts : true
|
266
277
|
}, options || {});
|
278
|
+
|
279
|
+
this._bindCallbacks();
|
267
280
|
},
|
268
281
|
_makeRequest : function(options) {
|
269
282
|
if (options.update) new Ajax.Updater(options.update, options.url, options);
|
270
283
|
else new Ajax.Request(options.url, options);
|
271
284
|
return false;
|
285
|
+
},
|
286
|
+
_bindCallbacks: function() {
|
287
|
+
$w('onCreate onComplete onException onFailure onInteractive onLoading onLoaded onSuccess').each(function(cb) {
|
288
|
+
if (Object.isFunction(this.options[cb]))
|
289
|
+
this.options[cb] = this.options[cb].bind(this);
|
290
|
+
}.bind(this));
|
272
291
|
}
|
273
292
|
}
|
274
293
|
|
Binary file
|
Binary file
|
Binary file
|
@@ -1,7 +1,15 @@
|
|
1
1
|
html, body {color: #193440; background: url(../images/300-ACD3E6-fff.png) repeat-x #fff; }
|
2
|
-
.page-header {color: white; background: url(../images/
|
3
|
-
.page-header .navigation.main-nav a {
|
4
|
-
|
2
|
+
.page-header {color: white; background: url(../images/101-3B5F87-ACD3E6.png) repeat-x #3F606E;}
|
3
|
+
.page-header .navigation.main-nav a {
|
4
|
+
background: url(../images/30-3E547A-242E42.png) repeat-x #242E42;
|
5
|
+
}
|
6
|
+
.page-header .navigation.main-nav li.current a {
|
7
|
+
color: #222;
|
8
|
+
background: url(../images/30-DBE1E5-FCFEF5.png) repeat-x #FCFEF5;
|
9
|
+
border-top: 1px solid white;
|
10
|
+
border-left: 1px solid white;
|
11
|
+
border-right: 1px solid white;
|
12
|
+
}
|
5
13
|
.page-header .navigation.main-nav a:hover {background: #193440;}
|
6
14
|
.section.content {background: #FCFFF5;}
|
7
15
|
.button {color: white; background: #5B8BA0;}
|
@@ -77,7 +85,7 @@ form .actions input { margin: 0; }
|
|
77
85
|
margin: 0 40px 10px; padding: 10px 30px; border-width: 2px 0;
|
78
86
|
color: white;
|
79
87
|
}
|
80
|
-
.flash.notice {background: #
|
88
|
+
.flash.notice {background: #4E6A8F;}
|
81
89
|
.flash.error {background: #BC1C3D;}
|
82
90
|
.section.with-flash { padding-top: 20px; }
|
83
91
|
|
@@ -39,7 +39,7 @@ class HoboSubsiteGenerator < Rails::Generator::NamedBase
|
|
39
39
|
end
|
40
40
|
puts "Renaming app/views/taglibs/application.dryml to app/views/taglibs/front_site.dryml"
|
41
41
|
FileUtils.mv('app/views/taglibs/application.dryml', 'app/views/taglibs/front_site.dryml')
|
42
|
-
m.template "application.dryml", File.join('app/views/taglibs/application.dryml')
|
42
|
+
m.template "application.dryml", File.join('app/views/taglibs/application.dryml')
|
43
43
|
end
|
44
44
|
|
45
45
|
m.directory File.join('app', 'controllers', file_name)
|
@@ -38,6 +38,28 @@ class HoboUserControllerGenerator < Rails::Generator::NamedBase
|
|
38
38
|
m.template 'view.rhtml', path,
|
39
39
|
:assigns => { :action => action, :path => path }
|
40
40
|
end
|
41
|
+
|
42
|
+
if invite_only?
|
43
|
+
m.template "accept_invitation.dryml", File.join('app/views', class_path, file_name, "accept_invitation.dryml")
|
44
|
+
end
|
41
45
|
end
|
42
46
|
end
|
47
|
+
|
48
|
+
def invite_only?
|
49
|
+
options[:invite_only]
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
def banner
|
54
|
+
"Usage: #{$0} #{spec.name} ModelName [--invite-only]"
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_options!(opt)
|
58
|
+
opt.separator ''
|
59
|
+
opt.separator 'Options:'
|
60
|
+
opt.on("--invite-only", "Add features for an invite only website") do |v|
|
61
|
+
options[:invite_only] = true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
43
65
|
end
|
@@ -3,5 +3,27 @@ class <%= class_name %>Controller < ApplicationController
|
|
3
3
|
hobo_user_controller
|
4
4
|
|
5
5
|
auto_actions :all, :except => [ :index, :new, :create ]
|
6
|
+
<% if invite_only? -%>
|
7
|
+
|
8
|
+
def create
|
9
|
+
hobo_create do
|
10
|
+
if valid?
|
11
|
+
self.current_user = this
|
12
|
+
this.password = this.password_confirmation = nil # don't trigger password change validations
|
13
|
+
this.state = 'active'
|
14
|
+
this.save
|
15
|
+
flash[:notice] = "You are now the site administrator"
|
16
|
+
redirect_to home_page
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def do_accept_invitation
|
22
|
+
do_transition_action :accept_invitation do
|
23
|
+
self.current_user = this
|
24
|
+
flash[:notice] = "You have signed up"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
<% end -%>
|
6
28
|
|
7
29
|
end
|
@@ -19,12 +19,28 @@ class HoboUserModelGenerator < Rails::Generator::NamedBase
|
|
19
19
|
|
20
20
|
m.template 'mailer.rb', File.join('app/models', class_path, "#{file_name}_mailer.rb")
|
21
21
|
m.template 'forgot_password.erb', File.join(mailer_dir, "forgot_password.erb")
|
22
|
+
|
23
|
+
if invite_only?
|
24
|
+
m.template 'invite.erb', File.join(mailer_dir, "invite.erb")
|
25
|
+
end
|
22
26
|
end
|
23
27
|
end
|
28
|
+
|
29
|
+
def invite_only?
|
30
|
+
options[:invite_only]
|
31
|
+
end
|
24
32
|
|
25
33
|
protected
|
26
34
|
def banner
|
27
|
-
"Usage: #{$0} #{spec.name} ModelName"
|
35
|
+
"Usage: #{$0} #{spec.name} ModelName [--invite-only]"
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_options!(opt)
|
39
|
+
opt.separator ''
|
40
|
+
opt.separator 'Options:'
|
41
|
+
opt.on("--invite-only", "Add features for an invite only website") do |v|
|
42
|
+
options[:invite_only] = true
|
43
|
+
end
|
28
44
|
end
|
29
45
|
|
30
46
|
end
|
@@ -10,5 +10,20 @@ class <%= class_name -%>Mailer < ActionMailer::Base
|
|
10
10
|
@sent_on = Time.now
|
11
11
|
@headers = {}
|
12
12
|
end
|
13
|
+
<% if invite_only? -%>
|
13
14
|
|
15
|
+
def invite(user, key)
|
16
|
+
host = Hobo::Controller.request_host
|
17
|
+
app_name = Hobo::Controller.app_name || host
|
18
|
+
# FIXME - nasty hack
|
19
|
+
app_name.remove!(/ - Admin$/)
|
20
|
+
@subject = "Invitation to #{app_name}"
|
21
|
+
@body = { :user => user, :key => key, :host => host, :app_name => app_name }
|
22
|
+
@recipients = user.email_address
|
23
|
+
@from = "no-reply@#{host}"
|
24
|
+
@sent_on = Time.now
|
25
|
+
@headers = {}
|
26
|
+
end
|
27
|
+
<% end -%>
|
28
|
+
|
14
29
|
end
|
@@ -11,19 +11,39 @@ class <%= class_name %> < ActiveRecord::Base
|
|
11
11
|
|
12
12
|
# This gives admin rights to the first sign-up.
|
13
13
|
# Just remove it if you don't want that
|
14
|
-
before_create { |user| user.administrator = true if
|
15
|
-
|
14
|
+
before_create { |user| user.administrator = true if !Rails.env.test? && count == 0 }
|
15
|
+
|
16
|
+
<% if invite_only? -%>
|
17
|
+
validates_confirmation_of :password, :if => "User.count == 0"
|
18
|
+
<% end -%>
|
16
19
|
|
17
20
|
# --- Signup lifecycle --- #
|
18
21
|
|
19
22
|
lifecycle do
|
20
23
|
|
24
|
+
<% if invite_only? -%>
|
25
|
+
state :invited, :default => true
|
26
|
+
state :active
|
27
|
+
|
28
|
+
create :invite,
|
29
|
+
:available_to => "acting_user if acting_user.administrator?",
|
30
|
+
:params => [:name, :email_address],
|
31
|
+
:new_key => true,
|
32
|
+
:become => :invited do
|
33
|
+
UserMailer.deliver_invite(self, lifecycle.key)
|
34
|
+
end
|
35
|
+
|
36
|
+
transition :accept_invitation, { :invited => :active }, :available_to => :key_holder,
|
37
|
+
:params => [ :password, :password_confirmation ]
|
38
|
+
|
39
|
+
<% else -%>
|
21
40
|
state :active, :default => true
|
22
41
|
|
23
42
|
create :signup, :available_to => "Guest",
|
24
43
|
:params => [:name, :email_address, :password, :password_confirmation],
|
25
44
|
:become => :active
|
26
|
-
|
45
|
+
|
46
|
+
<% end -%>
|
27
47
|
transition :request_password_reset, { :active => :active }, :new_key => true do
|
28
48
|
<%= class_name %>Mailer.deliver_forgot_password(self, lifecycle.key)
|
29
49
|
end
|
@@ -37,11 +57,18 @@ class <%= class_name %> < ActiveRecord::Base
|
|
37
57
|
# --- Permissions --- #
|
38
58
|
|
39
59
|
def create_permitted?
|
60
|
+
<% if invite_only? -%>
|
61
|
+
# Only the initial admin user can be created, from there it's invite-only
|
62
|
+
User.count == 0
|
63
|
+
<% else -%>
|
40
64
|
false
|
65
|
+
<% end -%>
|
41
66
|
end
|
42
67
|
|
43
68
|
def update_permitted?
|
44
|
-
acting_user.administrator? ||
|
69
|
+
acting_user.administrator? ||
|
70
|
+
(acting_user == self && only_changed?(:email_address, :crypted_password,
|
71
|
+
:current_password, :password, :password_confirmation))
|
45
72
|
# Note: crypted_password has attr_protected so although it is permitted to change, it cannot be changed
|
46
73
|
# directly from a form submission.
|
47
74
|
end
|