hobo 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
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