hobo 0.6.1 → 0.6.2

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/bin/hobo +3 -2
  2. data/hobo_files/plugin/CHANGES.txt +299 -2
  3. data/hobo_files/plugin/Rakefile +12 -10
  4. data/hobo_files/plugin/generators/hobo/templates/guest.rb +1 -13
  5. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +11 -7
  6. data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +1 -0
  7. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +1 -1
  8. data/hobo_files/plugin/generators/hobo_rapid/templates/lowpro.js +405 -0
  9. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +1 -1
  10. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +1 -9
  11. data/hobo_files/plugin/init.rb +5 -0
  12. data/hobo_files/plugin/lib/active_record/has_many_association.rb +1 -1
  13. data/hobo_files/plugin/lib/extensions.rb +26 -5
  14. data/hobo_files/plugin/lib/extensions/test_case.rb +1 -1
  15. data/hobo_files/plugin/lib/hobo.rb +37 -11
  16. data/hobo_files/plugin/lib/hobo/authenticated_user.rb +7 -2
  17. data/hobo_files/plugin/lib/hobo/authentication_support.rb +7 -6
  18. data/hobo_files/plugin/lib/hobo/composite_model.rb +5 -0
  19. data/hobo_files/plugin/lib/hobo/controller.rb +4 -4
  20. data/hobo_files/plugin/lib/hobo/dryml.rb +5 -5
  21. data/hobo_files/plugin/lib/hobo/dryml/part_context.rb +3 -6
  22. data/hobo_files/plugin/lib/hobo/dryml/template.rb +16 -15
  23. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +24 -20
  24. data/hobo_files/plugin/lib/hobo/email_address.rb +4 -0
  25. data/hobo_files/plugin/lib/hobo/field_spec.rb +2 -1
  26. data/hobo_files/plugin/lib/hobo/guest.rb +21 -0
  27. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +42 -2
  28. data/hobo_files/plugin/lib/hobo/http_parameters.rb +225 -0
  29. data/hobo_files/plugin/lib/hobo/model.rb +55 -37
  30. data/hobo_files/plugin/lib/hobo/model_controller.rb +151 -151
  31. data/hobo_files/plugin/lib/hobo/model_queries.rb +30 -5
  32. data/hobo_files/plugin/lib/hobo/user_controller.rb +27 -16
  33. data/hobo_files/plugin/lib/hobo/where_fragment.rb +6 -1
  34. data/hobo_files/plugin/tags/rapid.dryml +88 -58
  35. data/hobo_files/plugin/tags/rapid_document_tags.dryml +5 -5
  36. data/hobo_files/plugin/tags/rapid_editing.dryml +3 -3
  37. data/hobo_files/plugin/tags/rapid_forms.dryml +35 -26
  38. data/hobo_files/plugin/tags/rapid_navigation.dryml +13 -12
  39. data/hobo_files/plugin/tags/rapid_pages.dryml +35 -31
  40. data/hobo_files/plugin/tags/rapid_plus.dryml +41 -0
  41. data/hobo_files/plugin/tags/rapid_support.dryml +18 -9
  42. data/hobo_files/plugin/tasks/dump_fixtures.rake +61 -0
  43. metadata +7 -11
  44. data/hobo_files/plugin/spec/fixtures/users.yml +0 -9
  45. data/hobo_files/plugin/spec/spec.opts +0 -6
  46. data/hobo_files/plugin/spec/spec_helper.rb +0 -28
  47. data/hobo_files/plugin/spec/unit/hobo/dryml/template_spec.rb +0 -650
@@ -45,6 +45,11 @@ class ActiveRecord::Base
45
45
  def self.hobo_model
46
46
  include Hobo::Model
47
47
  end
48
+ def self.hobo_user_model(login_attr=nil, &b)
49
+ include Hobo::Model
50
+ include Hobo::AuthenticatedUser
51
+ set_login_attr(login_attr, &b) if login_attr
52
+ end
48
53
  end
49
54
 
50
55
  # Default settings
@@ -40,7 +40,7 @@ module ActiveRecord::Associations
40
40
 
41
41
  def find_with_block(*args, &b)
42
42
  if b
43
- options = extract_options_from_args!(args)
43
+ options = args.extract_options!
44
44
  args << options.merge(:conditions => member_class.conditions(&b))
45
45
  find_without_block(*args)
46
46
  else
@@ -132,10 +132,31 @@ class Object
132
132
  def class_def name, &blk
133
133
  class_eval { define_method name, &blk }
134
134
  end
135
+
136
+ def _?()
137
+ self
138
+ end
135
139
 
136
140
  end
137
141
 
138
142
 
143
+ class NilClass
144
+ def _?()
145
+ SafeNil.instance
146
+ end
147
+ end
148
+
149
+
150
+ class SafeNil
151
+ def self.instance
152
+ @instance ||= SafeNil.new
153
+ end
154
+
155
+ def method_missing(*args, &b)
156
+ nil.send(*args, &b) rescue nil
157
+ end
158
+ end
159
+
139
160
 
140
161
  module Enumerable
141
162
 
@@ -245,14 +266,14 @@ end
245
266
  class HashWithIndifferentAccess
246
267
 
247
268
  def -(keys)
248
- res = {}
269
+ res = HashWithIndifferentAccess.new
249
270
  keys = keys.map {|k| k.is_a?(Symbol) ? k.to_s : k }
250
271
  each_pair { |k, v| res[k] = v unless k.in?(keys) }
251
272
  res
252
273
  end
253
274
 
254
275
  def &(keys)
255
- res = {}
276
+ res = HashWithIndifferentAccess.new
256
277
  keys.each do |k|
257
278
  k = k.to_s if k.is_a?(Symbol)
258
279
  res[k] = self[k] if has_key?(k)
@@ -261,9 +282,9 @@ class HashWithIndifferentAccess
261
282
  end
262
283
 
263
284
  def partition_hash(keys=nil)
264
- keys = keys.map {|k| k.is_a?(Symbol) ? k.to_s : k }
265
- yes = {}
266
- no = {}
285
+ keys = keys._?.map {|k| k.is_a?(Symbol) ? k.to_s : k }
286
+ yes = HashWithIndifferentAccess.new
287
+ no = HashWithIndifferentAccess.new
267
288
  each do |k,v|
268
289
  if block_given? ? yield(k,v) : keys.include?(k)
269
290
  yes[k] = v
@@ -31,7 +31,7 @@ class Test::Unit::TestCase
31
31
 
32
32
  def logs_in_as(user, password)
33
33
  @current_user = user
34
- post login_url, :login => user.login, :password => password
34
+ post login_url(user), :login => user.login, :password => password
35
35
  assert_response :redirect, "#{user.login} failed to log in"
36
36
  get homepage_url
37
37
  assert_response :success
@@ -186,11 +186,12 @@ module Hobo
186
186
  end
187
187
 
188
188
  if controller < Hobo::UserController
189
- map.named_route("#{web_name.singularize}_login", "#{web_name.singularize}_login",
189
+ prefix = web_name == "users" ? "" : "#{web_name.singularize}_"
190
+ map.named_route("#{web_name.singularize}_login", "#{prefix}login",
190
191
  :controller => web_name, :action => 'login')
191
- map.named_route("#{web_name.singularize}_logout", "#{web_name.singularize}_logout",
192
+ map.named_route("#{web_name.singularize}_logout", "#{prefix}logout",
192
193
  :controller => web_name, :action => 'logout')
193
- map.named_route("#{web_name.singularize}_signup", "#{web_name.singularize}_signup",
194
+ map.named_route("#{web_name.singularize}_signup", "#{prefix}signup",
194
195
  :controller => web_name, :action => 'signup')
195
196
  end
196
197
  end
@@ -219,7 +220,7 @@ module Hobo
219
220
 
220
221
 
221
222
  def simple_has_many_association?(array_or_reflection)
222
- refl = array_or_reflection.is_a?(Array) ? array_or_reflection.proxy_reflection : array_or_reflection
223
+ refl = array_or_reflection.respond_to?(:proxy_reflection) ? array_or_reflection.proxy_reflection : array_or_reflection
223
224
  return false unless refl.is_a?(ActiveRecord::Reflection::AssociationReflection)
224
225
  refl.macro == :has_many and
225
226
  (not refl.through_reflection) and
@@ -236,6 +237,24 @@ module Hobo
236
237
  end
237
238
 
238
239
 
240
+ def get_field_path(object, path)
241
+ path = if path.is_a? Array
242
+ path
243
+ elsif path.is_a? String
244
+ path.split('.')
245
+ else
246
+ [path]
247
+ end
248
+
249
+ field, parent = nil
250
+ path.each do |field|
251
+ parent = object
252
+ object = get_field(parent, field)
253
+ end
254
+ [parent, field, object]
255
+ end
256
+
257
+
239
258
  # --- Permissions --- #
240
259
 
241
260
 
@@ -265,7 +284,7 @@ module Hobo
265
284
  # has_many and polymorphic associations are not editable (for now)
266
285
  return false if refl and (refl.macro == :has_many or refl.options[:polymorphic] or refl.macro == :has_one)
267
286
 
268
- if object.respond_to?(:editable_by?)
287
+ if object.has_hobo_method?(:editable_by?)
269
288
  check_permission(:edit, person, object, field.to_sym)
270
289
  else
271
290
  # Fake an edit test by setting the field in question to
@@ -288,7 +307,8 @@ module Hobo
288
307
  Hobo::Undefined.new
289
308
  end)
290
309
  rescue Hobo::UndefinedAccessError
291
- raise HoboError, "#{object.class.name} does not support undefined assignements, define editable_by(user, field)"
310
+ raise HoboError, ("#{object.class.name}##{field} does not support undefined assignements, " +
311
+ "define editable_by?(user, field)")
292
312
  end
293
313
 
294
314
  begin
@@ -318,6 +338,12 @@ module Hobo
318
338
  # proxy, we don't want to loose the information that the object
319
339
  # belongs_to the proxy owner.
320
340
  def can_view?(person, object, field=nil)
341
+ # Field can be a dot separated path
342
+ if field && field.is_a?(String) && (path = field.split(".")).length > 1
343
+ _, _, object = get_field_path(object, path[0..-2])
344
+ field = path.last
345
+ end
346
+
321
347
  if field
322
348
  field = field.to_sym if field.is_a? String
323
349
  return false if object.is_a?(ActiveRecord::Base) and object.class.never_show?(field)
@@ -341,10 +367,10 @@ module Hobo
341
367
 
342
368
 
343
369
  def can_call?(person, object, method)
344
- return true if person.respond_to?(:super_user?) && person.super_user?
370
+ return true if person.has_hobo_method?(:super_user?) && person.super_user?
345
371
 
346
372
  m = "#{method}_callable_by?"
347
- object.respond_to?(m) && object.send(m, person)
373
+ object.has_hobo_method?(m) && object.send(m, person)
348
374
  end
349
375
 
350
376
  # --- end permissions -- #
@@ -368,7 +394,7 @@ module Hobo
368
394
 
369
395
 
370
396
  def check_permission(permission, person, object, *args)
371
- return true if person.respond_to?(:super_user?) and person.super_user?
397
+ return true if person.has_hobo_method?(:super_user?) and person.super_user?
372
398
 
373
399
  return true if permission == :view && !(object.is_a?(ActiveRecord::Base) || object.is_a?(Hobo::CompositeModel))
374
400
 
@@ -379,7 +405,7 @@ module Hobo
379
405
  when :edit; :editable_by?
380
406
  when :view; :viewable_by?
381
407
  end
382
- p = if object.respond_to?(obj_method)
408
+ p = if object.has_hobo_method?(obj_method)
383
409
  begin
384
410
  object.send(obj_method, person, *args)
385
411
  rescue Hobo::UndefinedAccessError
@@ -389,7 +415,7 @@ module Hobo
389
415
  object.class.send(obj_method, person, *args)
390
416
  elsif !object.is_a?(Class) # No user fallback for class-level permissions
391
417
  person_method = "can_#{permission}?".to_sym
392
- if person.respond_to?(person_method)
418
+ if person.has_hobo_method?(person_method)
393
419
  person.send(person_method, object, *args)
394
420
  else
395
421
  # The object does not define permissions - you can only view it
@@ -57,6 +57,7 @@ module Hobo
57
57
  validates_uniqueness_of attr, :case_sensitive => false
58
58
  end
59
59
  end
60
+ def login_attr; @login_attr; end
60
61
 
61
62
  # Authenticates a user by their login name and unencrypted password. Returns the user or nil.
62
63
  def authenticate(login, password)
@@ -110,6 +111,10 @@ module Hobo
110
111
  save(false)
111
112
  end
112
113
 
114
+ def guest?
115
+ false
116
+ end
117
+
113
118
  protected
114
119
  # Before filter that encrypts the password before having it stored in the database.
115
120
  def encrypt_password
@@ -118,11 +123,11 @@ module Hobo
118
123
  self.crypted_password = encrypt(password)
119
124
  end
120
125
 
121
- # Is a password required for login? (or do we have an empty password?
126
+ # Is a password required for login? (or do we have an empty password?)
122
127
  def password_required?
123
128
  (crypted_password.blank? && password != nil) || !password.blank?
124
129
  end
125
-
130
+
126
131
  end
127
132
 
128
133
  end
@@ -6,7 +6,8 @@ module Hobo
6
6
  def logged_in?
7
7
  not current_user.guest?
8
8
  end
9
-
9
+
10
+
10
11
  # Check if the user is authorized.
11
12
  #
12
13
  # Override this method in your controllers if you want to restrict access
@@ -37,15 +38,15 @@ module Hobo
37
38
  # skip_before_filter :login_required
38
39
  #
39
40
  def login_required(user_model=nil)
41
+ auth_model = user_model || UserController.user_models.first
40
42
  if current_user.guest?
41
- auth_model = user_model || UserController.user_models.first
42
43
  username, passwd = get_auth_data
43
- self.current_user = auth_model.authenticate(username, passwd) || :false if username && passwd && auth_model
44
+ self.current_user = auth_model.authenticate(username, passwd) || nil if username && passwd && auth_model
44
45
  end
45
46
  if logged_in? && authorized? && (user_model.nil? || current_user.is_a?(user_model))
46
47
  true
47
48
  else
48
- access_denied
49
+ access_denied(auth_model)
49
50
  end
50
51
  end
51
52
 
@@ -57,11 +58,11 @@ module Hobo
57
58
  # behavior in case the user is not authorized
58
59
  # to access the requested action. For example, a popup window might
59
60
  # simply close itself.
60
- def access_denied
61
+ def access_denied(user_model)
61
62
  respond_to do |accepts|
62
63
  accepts.html do
63
64
  store_location
64
- redirect_to login_url
65
+ redirect_to(login_url(user_model))
65
66
  end
66
67
  accepts.xml do
67
68
  headers["Status"] = "Unauthorized"
@@ -43,6 +43,11 @@ module Hobo
43
43
  end
44
44
 
45
45
 
46
+ def has_hobo_method?(name)
47
+ respond_to?(name)
48
+ end
49
+
50
+
46
51
  def compose_with(object, use=nil)
47
52
  self_classes = use ? use.models : self.class.models
48
53
  from_self = (self_classes - [object.class.name]).map {|classname| send(classname.underscore)}
@@ -83,8 +83,8 @@ module Hobo
83
83
 
84
84
  def render_tag(tag, options={}, render_options={})
85
85
  add_variables_to_assigns
86
- render({:text => Hobo::Dryml.render_tag(@template, tag, options),
87
- :layout => false }.merge(render_options))
86
+ text = Hobo::Dryml.render_tag(@template, tag, options)
87
+ text && render({:text => text, :layout => false }.merge(render_options))
88
88
  end
89
89
 
90
90
 
@@ -98,7 +98,7 @@ module Hobo
98
98
  dryml_renderer.send(tag, options.merge(:with => o))
99
99
  end.join
100
100
 
101
- render :text => results + dryml_renderer.part_contexts_js
101
+ render :text => results + dryml_renderer.part_contexts_storage
102
102
  end
103
103
 
104
104
 
@@ -114,7 +114,7 @@ module Hobo
114
114
 
115
115
  # Store the given user in the session.
116
116
  def current_user=(new_user)
117
- session[:user] = (new_user.nil? || new_user.is_a?(Symbol)) ? nil : new_user.typed_id
117
+ session[:user] = (new_user.nil? || new_user.guest?) ? nil : new_user.typed_id
118
118
  @current_user = new_user
119
119
  end
120
120
 
@@ -26,6 +26,7 @@ module Hobo::Dryml
26
26
  CORE_TAGLIB = "plugins/hobo/tags/core"
27
27
 
28
28
  @renderer_classes = {}
29
+ @tag_page_renderer_classes = {}
29
30
 
30
31
  class << self
31
32
 
@@ -33,7 +34,7 @@ module Hobo::Dryml
33
34
 
34
35
  def clear_cache
35
36
  @renderer_classes = {}
36
- @tag_page_renderer_class = nil
37
+ @tag_page_renderer_classes = {}
37
38
  end
38
39
 
39
40
  def render_tag(view, tag, options={})
@@ -61,10 +62,9 @@ module Hobo::Dryml
61
62
  end
62
63
 
63
64
  if page == EMPTY_PAGE
64
- @tag_page_renderer_class = make_renderer_class("", EMPTY_PAGE, local_names,
65
- [Hobo::HoboHelper, ApplicationHelper], included_taglibs) if
66
- @tag_page_renderer_class.nil?
67
- @tag_page_renderer_class.new(page, view)
65
+ @tag_page_renderer_classes[view.controller.class.name] ||= make_renderer_class("", EMPTY_PAGE, local_names,
66
+ [Hobo::HoboHelper, ApplicationHelper], included_taglibs)
67
+ @tag_page_renderer_classes[view.controller.class.name].new(page, view)
68
68
  else
69
69
  page ||= view.instance_variable_get('@hobo_template_path')
70
70
  template_path = "app/views/" + page + ".dryml"
@@ -43,9 +43,9 @@ module Hobo
43
43
 
44
44
  def marshal(session)
45
45
  context = [@part_name, @this_id, @locals]
46
- puts "STORE: " + context.inspect
47
46
  data = Base64.encode64(Marshal.dump(context)).strip
48
- "#{data}--#{self.class.generate_digest(data, session)}"
47
+ digest = self.class.generate_digest(data, session)
48
+ "#{data}--#{digest}"
49
49
  end
50
50
 
51
51
 
@@ -66,11 +66,8 @@ module Hobo
66
66
  raise TamperedWithPartContext unless digest == generate_digest(data, session)
67
67
 
68
68
  part_name, this_id, locals = Marshal.load(Base64.decode64(data))
69
- puts "RESTORE: " + [part_name, this_id, locals].inspect
70
69
 
71
- x = [part_name, restore_part_this(this_id, this), restore_locals(locals)]
72
- puts this_id, x.inspect
73
- x
70
+ [part_name, restore_part_this(this_id, this), restore_locals(locals)]
74
71
  end
75
72
 
76
73
  def restore_part_this(this_id, this)
@@ -199,9 +199,10 @@ module Hobo::Dryml
199
199
 
200
200
  def set_element(el)
201
201
  assigns = el.attributes.map do |name, value|
202
- dryml_exception(el, "invalid name in set") unless name =~ DRYML_NAME_RX
202
+ dryml_exception(el, "invalid name in set") unless name =~ /^#{DRYML_NAME}(\.#{DRYML_NAME})*$/
203
203
  "#{name} = #{attribute_to_ruby(value)}; "
204
204
  end.join
205
+ code = apply_control_attributes("begin; #{assigns}; end", el)
205
206
  "<% #{assigns}#{tag_newlines(el)} %>"
206
207
  end
207
208
 
@@ -373,7 +374,8 @@ module Hobo::Dryml
373
374
 
374
375
  def default_tagbody_element(el)
375
376
  name = el.attributes['for'] || @containing_tag_name
376
- "<% #{name}_default_tagbody && #{name}_default_tagbody.call %>"
377
+ local_name = default_tagbody_name(name)
378
+ "<% #{local_name} && #{local_name}.call %>"
377
379
  end
378
380
 
379
381
 
@@ -464,7 +466,7 @@ module Hobo::Dryml
464
466
  else
465
467
  if is_param_default_call
466
468
  # The tag is a proc available in a local variable
467
- "#{name}__default.call(#{attributes}, #{parameters})"
469
+ "#{default_parameter_name(name)}.call(#{attributes}, #{parameters})"
468
470
  elsif (call_type = polymorphic_call_type(el))
469
471
  "send(find_polymorphic_template(:#{name}, #{call_type}), #{attributes}, #{parameters})"
470
472
  else
@@ -532,9 +534,7 @@ module Hobo::Dryml
532
534
  dryml_exception("replace attribute must not have a value", el) if repl.has_rhs?
533
535
  dryml_exception("replace parameters must not have attributes", el) if el.attributes.length > 1
534
536
 
535
- default_tag_name = "#{el.dryml_name}__default"
536
-
537
- "proc {|#{default_tag_name}| new_context { %>#{children_to_erb(el)}<% } #{nl}}"
537
+ "proc {|#{default_parameter_name(el.dryml_name)}| new_context { %>#{children_to_erb(el)}<% } #{nl}}"
538
538
  else
539
539
  attributes = el.attributes.map do
540
540
  |name, value| ":#{name} => #{attribute_to_ruby(value, el)}" unless name.in?(SPECIAL_ATTRIBUTES)
@@ -550,7 +550,7 @@ module Hobo::Dryml
550
550
  body = children_to_erb(el)
551
551
  @containing_tag_name = old
552
552
 
553
- attributes << ":tagbody => proc {|#{el.dryml_name}_default_tagbody| new_context { %>#{body}<% } } "
553
+ attributes << ":tagbody => proc {|#{default_tagbody_name(el.dryml_name)}| new_context { %>#{body}<% } } "
554
554
  end
555
555
  "proc { {#{attributes * ', '}} #{nl}}"
556
556
  end
@@ -558,12 +558,13 @@ module Hobo::Dryml
558
558
  end
559
559
 
560
560
 
561
- def default_tag_name(el)
562
- if template_call?(el)
563
- "Default#{el.dryml_name}"
564
- else
565
- "default_#{el.dryml_name}"
566
- end
561
+ def default_parameter_name(name)
562
+ "_#{name}__default"
563
+ end
564
+
565
+
566
+ def default_tagbody_name(name)
567
+ "_#{name}_default_tagbody"
567
568
  end
568
569
 
569
570
 
@@ -589,7 +590,7 @@ module Hobo::Dryml
589
590
  "call_block_tag_parameter(#{to_call}, #{attributes}, all_parameters[#{param_name}])"
590
591
  else
591
592
  if is_param_default_call
592
- "#{name}__default.call_with_block(#{attributes})"
593
+ "#{default_parameter_name(name)}.call_with_block(#{attributes})"
593
594
  elsif (call_type = polymorphic_call_type(el))
594
595
  "send(find_polymorphic_tag(:#{name}, #{call_type}), #{attributes})"
595
596
  else
@@ -606,7 +607,7 @@ module Hobo::Dryml
606
607
  children = children_to_erb(el)
607
608
  @containing_tag_name = old
608
609
 
609
- call_statement = "#{call} do |#{el.dryml_name}_default_tagbody| #{newlines}%>#{children}<% end"
610
+ call_statement = "#{call} do |#{default_tagbody_name(el.dryml_name)}| #{newlines}%>#{children}<% end"
610
611
  call = "<% _output(" + apply_control_attributes(call_statement, el) + ") %>"
611
612
  maybe_make_part_call(el, call)
612
613
  end