hobo 0.9.0 → 0.9.100

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 (47) hide show
  1. data/CHANGES.txt +132 -0
  2. data/Rakefile +18 -22
  3. data/bin/hobo +14 -13
  4. data/doctest/scopes.rdoctest +1 -0
  5. data/dryml_generators/rapid/forms.dryml.erb +1 -1
  6. data/dryml_generators/rapid/pages.dryml.erb +24 -24
  7. data/lib/hobo.rb +3 -3
  8. data/lib/hobo/accessible_associations.rb +10 -3
  9. data/lib/hobo/controller.rb +2 -1
  10. data/lib/hobo/dryml.rb +7 -2
  11. data/lib/hobo/dryml/dryml_builder.rb +1 -4
  12. data/lib/hobo/dryml/dryml_generator.rb +5 -3
  13. data/lib/hobo/dryml/taglib.rb +3 -8
  14. data/lib/hobo/dryml/template.rb +3 -10
  15. data/lib/hobo/dryml/template_handler.rb +3 -2
  16. data/lib/hobo/hobo_helper.rb +5 -83
  17. data/lib/hobo/lifecycles.rb +9 -5
  18. data/lib/hobo/lifecycles/actions.rb +5 -5
  19. data/lib/hobo/lifecycles/lifecycle.rb +6 -2
  20. data/lib/hobo/model.rb +14 -36
  21. data/lib/hobo/model_controller.rb +28 -15
  22. data/lib/hobo/model_router.rb +1 -1
  23. data/lib/hobo/permissions.rb +55 -5
  24. data/lib/hobo/permissions/associations.rb +13 -5
  25. data/lib/hobo/rapid_helper.rb +4 -10
  26. data/lib/hobo/scopes/automatic_scopes.rb +9 -1
  27. data/lib/hobo/translations.rb +88 -0
  28. data/lib/hobo/user.rb +1 -2
  29. data/lib/hobo/user_controller.rb +31 -9
  30. data/lib/hobo/view_hints.rb +38 -6
  31. data/rails_generators/hobo_model/templates/hints.rb +4 -1
  32. data/rails_generators/hobo_model_controller/hobo_model_controller_generator.rb +1 -1
  33. data/rails_generators/hobo_rapid/hobo_rapid_generator.rb +2 -1
  34. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +9 -0
  35. data/rails_generators/hobo_rapid/templates/ie7-recalc.js +166 -2
  36. data/rails_generators/hobo_user_model/templates/model.rb +1 -3
  37. data/taglibs/rapid.dryml +2 -1
  38. data/taglibs/rapid_core.dryml +7 -5
  39. data/taglibs/rapid_editing.dryml +14 -10
  40. data/taglibs/rapid_forms.dryml +7 -5
  41. data/taglibs/rapid_generics.dryml +1 -1
  42. data/taglibs/rapid_lifecycles.dryml +3 -2
  43. data/taglibs/rapid_pages.dryml +1 -1
  44. data/taglibs/rapid_support.dryml +1 -1
  45. data/tasks/hobo_tasks.rake +10 -0
  46. metadata +7 -7
  47. data/lib/hobo/bundle.rb +0 -330
@@ -31,7 +31,6 @@ module Hobo
31
31
  remember_token_expires_at :datetime
32
32
  end
33
33
 
34
- validates_presence_of :password_confirmation, :if => :new_password_required?
35
34
  validates_confirmation_of :password, :if => :new_password_required?
36
35
  password_validations
37
36
  validate :validate_current_password_when_changing_password
@@ -167,7 +166,7 @@ module Hobo
167
166
 
168
167
 
169
168
  def validate_current_password_when_changing_password
170
- changing_password? && !authenticated?(current_password) and errors.add :current_password, ht(:hobo.messages.current_password_is_not_correct, :default["is not correct"])
169
+ changing_password? && !authenticated?(current_password) and errors.add :current_password, Hobo::Translations.ht("hobo.messages.current_password_is_not_correct", :default => "is not correct")
171
170
  end
172
171
 
173
172
  end
@@ -54,7 +54,13 @@ module Hobo
54
54
  private
55
55
 
56
56
  def hobo_login(options={})
57
- (redirect_to home_page; return) if logged_in?
57
+ if logged_in?
58
+ respond_to do |wants|
59
+ wants.html { redirect_to home_page }
60
+ wants.js { hobo_ajax_response }
61
+ end
62
+ return
63
+ end
58
64
 
59
65
  login_attr = model.login_attribute.to_s.titleize.downcase
60
66
  options.reverse_merge!(:success_notice => ht(:"users.messages.login.success", :default=>["You have logged in."]),
@@ -73,14 +79,24 @@ module Hobo
73
79
  if !user.account_active?
74
80
  # account not activate - cancel this login
75
81
  self.current_user = old_user
76
- render :action => :account_disabled unless performed?
82
+ unless performed?
83
+ respond_to do |wants|
84
+ wants.html {render :action => :account_disabled}
85
+ wants.js {hobo_ajax_response}
86
+ end
87
+ end
77
88
  else
78
89
  if params[:remember_me].present?
79
90
  current_user.remember_me
80
91
  create_auth_cookie
81
92
  end
82
93
  flash[:notice] ||= options[:success_notice]
83
- redirect_back_or_default(options[:redirect_to] || home_page) unless performed?
94
+ unless performed?
95
+ respond_to do |wants|
96
+ wants.html {redirect_back_or_default(options[:redirect_to] || home_page) }
97
+ wants.js {hobo_ajax_response}
98
+ end
99
+ end
84
100
  end
85
101
  end
86
102
  end
@@ -98,13 +114,13 @@ module Hobo
98
114
  do_creator_action(:signup) do
99
115
  if valid?
100
116
  flash[:notice] = ht(:"users.messages.signup.success", :default=>["Thanks for signing up!"])
101
- flash[:notice] << ht(:"users.messages.signup.must_activate", :default=>[" You must activate your account before you can log in. Please check your email."]) unless this.account_active?
102
117
  end
103
118
  response_block(&b) or if valid?
104
- if this.account_active?
105
- self.current_user = this if this.account_active?
119
+ self.current_user = this if this.account_active?
120
+ respond_to do |wants|
121
+ wants.html { redirect_back_or_default(home_page) }
122
+ wants.js { hobo_ajax_response }
106
123
  end
107
- redirect_back_or_default(home_page)
108
124
  end
109
125
  end
110
126
  end
@@ -127,7 +143,10 @@ module Hobo
127
143
  if user && (!block_given? || yield(user))
128
144
  user.lifecycle.request_password_reset!(:nobody)
129
145
  end
130
- render_tag :forgot_password_email_sent_page
146
+ respond_to do |wants|
147
+ wants.html { render_tag :forgot_password_email_sent_page }
148
+ wants.js { hobo_ajax_response}
149
+ end
131
150
  end
132
151
  end
133
152
 
@@ -137,7 +156,10 @@ module Hobo
137
156
  response_block(&b) or if valid?
138
157
  self.current_user = this
139
158
  flash[:notice] = ht(:"users.messages.reset_password", :default=>["Your password has been reset"])
140
- redirect_to home_page
159
+ respond_to do |wants|
160
+ wants.html { redirect_to(home_page) }
161
+ wants.js { hobo_ajax_response }
162
+ end
141
163
  end
142
164
  end
143
165
  end
@@ -26,17 +26,29 @@ module Hobo
26
26
  end
27
27
  end
28
28
  end
29
-
30
- setter :model_name, proc { name.sub(/Hints$/, "") }
31
-
29
+
32
30
  setter :field_names, {}
33
31
 
34
32
  setter :field_help, {}
35
33
 
36
- setter :children, proc { model.dependent_collections.sort_by(&:to_s) } do |*args|
34
+ setter :children, [] do |*args|
35
+ # Setting children also gives a default parent using the reverse association
36
+ child_model = model.reflections[args.first].klass
37
+ if child_model.view_hints.parent.nil? and !child_model.view_hints.parent_defined
38
+ parent = model.reverse_reflection(args.first)
39
+ child_model.view_hints.parent(parent.name, :undefined => true)
40
+ end
37
41
  args
38
42
  end
39
43
 
44
+ setter :parent, nil do |*args|
45
+ options = args.extract_options!
46
+ parent_defined(true) unless options[:undefined]
47
+ args.first
48
+ end
49
+
50
+ setter :parent_defined, nil
51
+
40
52
  setter :paginate?, proc { !sortable? }
41
53
 
42
54
  setter :sortable?, proc { defined?(ActiveRecord::Acts::List::InstanceMethods) &&
@@ -54,14 +66,34 @@ module Hobo
54
66
  # Accessors
55
67
 
56
68
  class << self
69
+
70
+ def _name
71
+ @_name ||= name.sub(/Hints$/, '')
72
+ end
57
73
 
74
+ def model_name(new_name=nil)
75
+ if new_name.nil?
76
+ @model_name ||= Hobo::Translations.ht("#{_name.tableize}.model_name", :default => _name.titleize)
77
+ else
78
+ @model_name = Hobo::Translations.ht("#{_name.tableize}.model_name", :default => new_name)
79
+ end
80
+ end
81
+
82
+ def model_name_plural(new_name=nil)
83
+ if new_name.nil?
84
+ @model_name_plural ||= Hobo::Translations.ht("#{_name.tableize}.model_name_plural", :default => model_name.pluralize)
85
+ else
86
+ @model_name_plural = Hobo::Translations.ht("#{_name.tableize}.model_name_plural", :default => new_name)
87
+ end
88
+ end
89
+
58
90
  def model
59
- @model ||= name.sub(/Hints$/, "").constantize
91
+ @model ||= _name.constantize
60
92
  end
61
93
 
62
94
 
63
95
  def field_name(field)
64
- field_names.fetch(field.to_sym, field.to_s.titleize)
96
+ Hobo::Translations.ht("#{_name.tableize}.#{field}", :default => field_names.fetch(field.to_sym, field.to_s.titleize))
65
97
  end
66
98
 
67
99
  def primary_children
@@ -1,4 +1,7 @@
1
1
  class <%= class_name %>Hints < Hobo::ViewHints
2
2
 
3
-
3
+ # model_name "My Model"
4
+ # field_names :field1 => "First Field", :field2 => "Second Field"
5
+ # field_help :field1 => "Enter what you want in this field"
6
+ # children :primary_collection1, :aside_collection1, :aside_collection2
4
7
  end
@@ -1,7 +1,7 @@
1
1
  class HoboModelControllerGenerator < Rails::Generator::NamedBase
2
2
 
3
3
  def initialize(args, options)
4
- args[0] = args[0].pluralize
4
+ args[0] = args[0].pluralize if args[0] && args[0]!='--help'
5
5
  super(args, options)
6
6
  end
7
7
 
@@ -21,7 +21,8 @@ class HoboRapidGenerator < Hobo::Generator
21
21
  create_all(m, "themes/clean/views", "app/views/taglibs/themes/clean")
22
22
 
23
23
  if with_admin_site?
24
- options = ["--make-front-site", "admin"]
24
+ options = ["admin"]
25
+ options.unshift "--make-front-site" unless File.exist?('app/views/taglibs/front_site.dryml')
25
26
  options.unshift "--invite-only" if invite_only?
26
27
  m.dependency 'hobo_admin_site', options
27
28
  end
@@ -193,6 +193,7 @@ var Hobo = {
193
193
  onLeaveHover: null,
194
194
  callback: function(form, val) {
195
195
  old = val
196
+ el.setAttribute("hobo-edit-text", val)
196
197
  return (Hobo.fieldSetParam(el, val) + "&" + updateParams)
197
198
  },
198
199
  onFailure: function(_, resp) {
@@ -200,6 +201,10 @@ var Hobo = {
200
201
  },
201
202
  onEnterEditMode: function() {
202
203
  var blank_message = el.getAttribute("hobo-blank-message")
204
+ var editable_text = el.getAttribute("hobo-edit-text")
205
+ if (editable_text) {
206
+ el.innerHTML = editable_text
207
+ }
203
208
  if (el.innerHTML.gsub("&nbsp;", " ") == blank_message) {
204
209
  el.innerHTML = ""
205
210
  } else {
@@ -785,6 +790,10 @@ Event.addBehavior({
785
790
  rows: 2, handleLineBreaks: false, okButton: true, cancelLink: true, okText: "Save", submitOnBlur: false
786
791
  }
787
792
  var ipe = Hobo._makeInPlaceEditor(this, options)
793
+ ipe.getText = function() {
794
+ // Be careful! we're not calling unescapeHTML() here!
795
+ return this.element.innerHTML
796
+ }
788
797
  }
789
798
  },
790
799
 
@@ -1,2 +1,166 @@
1
- /* IE7/IE8.js - copyright 2004-2008, Dean Edwards */
2
- (function(){if(!IE7.loaded)return;CLASSES=/\sie7_class\d+/g;IE7.CSS.extend({elements:{},handlers:[],reset:function(){this.removeEventHandlers();var a=this.elements;for(var b in a)a[b].runtimeStyle.cssText="";this.elements={};var a=IE7.Rule.elements;for(var b in a){with(a[b])className=className.replace(CLASSES,"")}IE7.Rule.elements={}},reload:function(){this.rules=[];this.getInlineStyles();this.screen.load();if(this.print)this.print.load();this.refresh();this.trash()},addRecalc:function(b,c,d,e){this.base(b,c,function(a){d(a);IE7.CSS.elements[a.uniqueID]=a},e)},recalc:function(){this.reset();this.base()},addEventHandler:function(a,b,c){a.attachEvent(b,c);this.handlers.push(arguments)},removeEventHandlers:function(){var a;while(a=this.handlers.pop()){a[0].detachEvent(a[1],a[2])}},getInlineStyles:function(){var a=document.getElementsByTagName("style"),b;for(var c=a.length-1;(b=a[c]);c--){if(!b.disabled&&!b.ie7){var d=b.cssText||b.innerHTML;this.styles.push(d);b.cssText=d}}},trash:function(){var a=document.styleSheets,b,c;for(c=0;c<a.length;c++){b=a[c];if(!b.ie7&&!b.cssText){b.cssText=b.cssText}}this.base()},getText:function(a){return a.cssText||this.base(a)}});IE7.CSS.addEventHandler(window,"onunload",function(){IE7.CSS.removeEventHandlers()});IE7.Rule.elements={};IE7.Rule.prototype.extend({add:function(a){this.base(a);IE7.Rule.elements[a.uniqueID]=a}});if(IE7.PseudoElement){IE7.PseudoElement.hash={};IE7.PseudoElement.prototype.extend({create:function(a){var b=this.selector+":"+a.uniqueID;if(!IE7.PseudoElement.hash[b]){IE7.PseudoElement.hash[b]=true;this.base(a)}}})}IE7.HTML.extend({elements:{},addRecalc:function(b,c){this.base(b,function(a){if(!this.elements[a.uniqueID]){c(a);this.elements[a.uniqueID]=a}})}});document.recalc=function(a){if(IE7.CSS.screen){if(a)IE7.CSS.reload();IE7.recalc()}}})();
1
+
2
+ // =========================================================================
3
+ // ie7-recalc.js
4
+ // =========================================================================
5
+
6
+ (function() {
7
+ /* ---------------------------------------------------------------------
8
+
9
+ This allows refreshing of IE7 style rules. If you modify the DOM
10
+ you can update IE7 by calling document.recalc().
11
+
12
+ This should be the LAST module included.
13
+
14
+ --------------------------------------------------------------------- */
15
+
16
+ if (!IE7.loaded) return;
17
+
18
+ // remove all IE7 classes from an element
19
+ CLASSES = /\sie7_class\d+/g;
20
+
21
+ IE7.CSS.extend({
22
+ // store for elements that have style properties calculated
23
+ elements: {},
24
+ handlers: [],
25
+
26
+ // clear IE7 classes and styles
27
+ reset: function() {
28
+ this.removeEventHandlers();
29
+ // reset IE7 classes here
30
+ var elements = this.elements;
31
+ for (var i in elements) elements[i].runtimeStyle.cssText = "";
32
+ this.elements = {};
33
+ // reset runtimeStyle here
34
+ var elements = IE7.Rule.elements;
35
+ for (var i in elements) {
36
+ with (elements[i]) className = className.replace(CLASSES, "");
37
+ }
38
+ IE7.Rule.elements = {};
39
+ },
40
+
41
+ reload: function() {
42
+ this.rules = [];
43
+ this.getInlineStyles();
44
+ this.screen.load();
45
+ if (this.print) this.print.load();
46
+ this.refresh();
47
+ this.trash();
48
+ },
49
+
50
+ addRecalc: function(propertyName, test, handler, replacement) {
51
+ // call the ancestor method to add a wrapped recalc method
52
+ this.base(propertyName, test, function(element) {
53
+ // execute the original recalc method
54
+ handler(element);
55
+ // store a reference to this element so we can clear its style later
56
+ IE7.CSS.elements[element.uniqueID] = element;
57
+ }, replacement);
58
+ },
59
+
60
+ recalc: function() {
61
+ // clear IE7 styles and classes
62
+ this.reset();
63
+ // execute the ancestor method to perform recalculations
64
+ this.base();
65
+ },
66
+
67
+ addEventHandler: function(element, type, handler) {
68
+ element.attachEvent(type, handler);
69
+ // store the handler so it can be detached later
70
+ this.handlers.push(arguments);
71
+ },
72
+
73
+ removeEventHandlers: function() {
74
+ var handler;
75
+ while (handler = this.handlers.pop()) {
76
+ handler[0].detachEvent(handler[1], handler[2]);
77
+ }
78
+ },
79
+
80
+ getInlineStyles: function() {
81
+ // load inline styles
82
+ var styleSheets = document.getElementsByTagName("style"), styleSheet;
83
+ for (var i = styleSheets.length - 1; (styleSheet = styleSheets[i]); i--) {
84
+ if (!styleSheet.disabled && !styleSheet.ie7) {
85
+ var cssText = styleSheet.cssText || styleSheet.innerHTML;
86
+ this.styles.push(cssText);
87
+ styleSheet.cssText = cssText;
88
+ }
89
+ }
90
+ },
91
+
92
+ trash: function() {
93
+ // trash the old style sheets
94
+ var styleSheets = document.styleSheets, styleSheet, i;
95
+ for (i = 0; i < styleSheets.length; i++) {
96
+ styleSheet = styleSheets[i];
97
+ if (!styleSheet.ie7 && !styleSheet.cssText && styleSheet.cssText != '') {
98
+ styleSheet.cssText = styleSheet.cssText;
99
+ }
100
+ }
101
+ this.base();
102
+ },
103
+
104
+ getText: function(styleSheet) {
105
+ return styleSheet.cssText || this.base(styleSheet);
106
+ }
107
+ });
108
+
109
+ // remove event handlers (they eat memory)
110
+ IE7.CSS.addEventHandler(window, "onunload", function() {
111
+ IE7.CSS.removeEventHandlers();
112
+ });
113
+
114
+ // store all elements with an IE7 class assigned
115
+ IE7.Rule.elements = {};
116
+
117
+ IE7.Rule.prototype.extend({
118
+ add: function(element) {
119
+ // execute the ancestor "add" method
120
+ this.base(element);
121
+ // store a reference to this element so we can clear its classes later
122
+ IE7.Rule.elements[element.uniqueID] = element;
123
+ }
124
+ });
125
+
126
+ // store created pseudo elements
127
+ if (IE7.PseudoElement) {
128
+ IE7.PseudoElement.hash = {};
129
+
130
+ IE7.PseudoElement.prototype.extend({
131
+ create: function(target) {
132
+ var key = this.selector + ":" + target.uniqueID;
133
+ if (!IE7.PseudoElement.hash[key]) {
134
+ IE7.PseudoElement.hash[key] = true;
135
+ this.base(target);
136
+ }
137
+ }
138
+ });
139
+ }
140
+
141
+ IE7.HTML.extend({
142
+ elements: {},
143
+
144
+ addRecalc: function(selector, handler) {
145
+ // call the ancestor method to add a wrapped recalc method
146
+ this.base(selector, function(element) {
147
+ if (!this.elements[element.uniqueID]) {
148
+ // execute the original recalc method
149
+ handler(element);
150
+ // store a reference to this element so that
151
+ // it is not "fixed" again
152
+ this.elements[element.uniqueID] = element;
153
+ }
154
+ });
155
+ }
156
+ });
157
+
158
+ // allow refreshing of IE7 fixes
159
+ document.recalc = function(reload) {
160
+ if (IE7.CSS.screen) {
161
+ if (reload) IE7.CSS.reload();
162
+ IE7.recalc();
163
+ }
164
+ };
165
+
166
+ })();
@@ -3,14 +3,12 @@ class <%= class_name %> < ActiveRecord::Base
3
3
  hobo_user_model # Don't put anything above this
4
4
 
5
5
  fields do
6
- name :string, :unique
6
+ name :string, :required, :unique
7
7
  email_address :email_address, :login => true
8
8
  administrator :boolean, :default => false
9
9
  timestamps
10
10
  end
11
11
 
12
- validates_presence_of :name
13
-
14
12
  # This gives admin rights to the first sign-up.
15
13
  # Just remove it if you don't want that
16
14
  before_create { |user| user.administrator = true if !Rails.env.test? && count == 0 }
@@ -18,4 +18,5 @@ The Rapid tag library makes web development go fast. The Rapid tag library is yo
18
18
  <include src="rapid_plus"/>
19
19
  <include src="rapid_generics"/>
20
20
  <include src="rapid_lifecycles"/>
21
- <include src="rapid_summary"/>
21
+ <include src="rapid_summary"/>
22
+ <include src="rapid_user_pages"/>
@@ -13,17 +13,19 @@
13
13
 
14
14
  - tag: The name of a tag to use inside the `<td>` to display the value. Defaults to `view`
15
15
 
16
- - show-non-editable: By default, if `tag` is set to `input`, fields for which the current user does not have edit permission
17
- will be skipped (the entire row is skipped). Set this attribute to keep them. (Note that `<input>` automatically degrades
18
- to `<view>` if the user does not have edit permission.)
19
-
16
+ - no-edit: Controls the behavior of `<input>` tags when the user doesn't have edit permission for a field.
17
+ - view: render the current value using the `<view>` tag
18
+ - disable: render the input as normal, but add HTML's `disabled` attribute
19
+ - skip: render nothing at all. This will omit the entire row (including the label)
20
+ - ignore: render the input normally. That is, don't even perform the edit check.
21
+
20
22
  -->
21
23
  <def tag="field-list" attrs="tag, no-edit">
22
24
  <% tag ||= scope.in_form ? "input" : "view"; no_edit ||= "skip" %>
23
25
  <labelled-item-list merge-attrs="&attributes - attrs_for(:with_fields)">
24
26
  <with-fields merge-attrs="&attributes & attrs_for(:with_fields)">
25
27
  <% field_name = this_field_name
26
- input_attrs = {:no_edit => no_edit} if tag == "input" && no_edit == "disable"
28
+ input_attrs = {:no_edit => no_edit} if tag == "input"
27
29
  -%>
28
30
  <labelled-item unless="&tag == 'input' && no_edit == 'skip' && !can_edit?">
29
31
  <item-label param="#{this_field.to_s.sub('?', '')}-label" unless="&field_name.blank?">