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.
Files changed (58) hide show
  1. data/CHANGES.txt +34 -0
  2. data/Rakefile +30 -24
  3. data/bin/hobo +30 -10
  4. data/doctest/hobo/hobo_helper.rdoctest +92 -0
  5. data/doctest/hobo/lifecycles.rdoctest +261 -0
  6. data/doctest/scopes.rdoctest +387 -0
  7. data/dryml_generators/rapid/forms.dryml.erb +3 -3
  8. data/dryml_generators/rapid/pages.dryml.erb +4 -4
  9. data/lib/active_record/viewhints_validations_interceptor.rb +1 -1
  10. data/lib/hobo.rb +1 -1
  11. data/lib/hobo/accessible_associations.rb +3 -3
  12. data/lib/hobo/authentication_support.rb +1 -1
  13. data/lib/hobo/dryml.rb +10 -0
  14. data/lib/hobo/dryml/taglib.rb +3 -5
  15. data/lib/hobo/hobo_helper.rb +3 -1
  16. data/lib/hobo/include_in_save.rb +1 -0
  17. data/lib/hobo/lifecycles/actions.rb +6 -2
  18. data/lib/hobo/model.rb +1 -1
  19. data/lib/hobo/model_controller.rb +34 -12
  20. data/lib/hobo/permissions.rb +1 -1
  21. data/lib/hobo/rapid_helper.rb +3 -0
  22. data/lib/hobo/scopes/association_proxy_extensions.rb +8 -2
  23. data/lib/hobo/scopes/automatic_scopes.rb +3 -3
  24. data/lib/hobo/user_controller.rb +2 -1
  25. data/rails_generators/hobo/hobo_generator.rb +1 -1
  26. data/rails_generators/hobo/templates/application.dryml +0 -2
  27. data/rails_generators/hobo_admin_site/hobo_admin_site_generator.rb +45 -0
  28. data/rails_generators/hobo_admin_site/templates/admin.css +2 -0
  29. data/rails_generators/hobo_admin_site/templates/application.dryml +1 -0
  30. data/rails_generators/hobo_admin_site/templates/controller.rb +13 -0
  31. data/rails_generators/hobo_admin_site/templates/site_taglib.dryml +32 -0
  32. data/rails_generators/hobo_admin_site/templates/users_index.dryml +5 -0
  33. data/rails_generators/hobo_front_controller/hobo_front_controller_generator.rb +7 -1
  34. data/rails_generators/hobo_front_controller/templates/index.dryml +16 -0
  35. data/rails_generators/hobo_rapid/hobo_rapid_generator.rb +31 -1
  36. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +5 -3
  37. data/rails_generators/hobo_rapid/templates/lowpro.js +40 -21
  38. data/rails_generators/hobo_rapid/templates/themes/clean/public/images/101-3B5F87-ACD3E6.png +0 -0
  39. data/rails_generators/hobo_rapid/templates/themes/clean/public/images/30-3E547A-242E42.png +0 -0
  40. data/rails_generators/hobo_rapid/templates/themes/clean/public/images/30-DBE1E5-FCFEF5.png +0 -0
  41. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +12 -4
  42. data/rails_generators/hobo_subsite/hobo_subsite_generator.rb +1 -1
  43. data/rails_generators/hobo_user_controller/hobo_user_controller_generator.rb +22 -0
  44. data/rails_generators/hobo_user_controller/templates/accept_invitation.dryml +5 -0
  45. data/rails_generators/hobo_user_controller/templates/controller.rb +22 -0
  46. data/rails_generators/hobo_user_model/hobo_user_model_generator.rb +17 -1
  47. data/rails_generators/hobo_user_model/templates/invite.erb +9 -0
  48. data/rails_generators/hobo_user_model/templates/mailer.rb +15 -0
  49. data/rails_generators/hobo_user_model/templates/model.rb +31 -4
  50. data/taglibs/rapid_core.dryml +25 -6
  51. data/taglibs/rapid_forms.dryml +65 -24
  52. data/taglibs/rapid_lifecycles.dryml +1 -1
  53. data/taglibs/rapid_navigation.dryml +2 -2
  54. data/taglibs/rapid_plus.dryml +4 -3
  55. metadata +151 -210
  56. data/Manifest +0 -155
  57. data/hobo.gemspec +0 -46
  58. data/rails_generators/hobo_rapid/templates/themes/clean/public/images/100-3B5F87-ACD3E6.png +0 -0
@@ -95,10 +95,10 @@ module Hobo
95
95
  if options[:accessible]
96
96
  class_eval %{
97
97
  def #{name}_with_accessible=(array_or_hash)
98
- items = Hobo::AccessibleAssociations.prepare_has_many_assignment(#{name}, :#{name}, array_or_hash)
99
- self.#{name}_without_accessible = items
98
+ __items = Hobo::AccessibleAssociations.prepare_has_many_assignment(#{name}, :#{name}, array_or_hash)
99
+ self.#{name}_without_accessible = __items
100
100
  # ensure the loaded array contains any changed records
101
- self.#{name}.proxy_target[0..-1] = items
101
+ self.#{name}.proxy_target[0..-1] = __items
102
102
  end
103
103
  }, __FILE__, __LINE__ - 7
104
104
  alias_method_chain :"#{name}=", :accessible
@@ -67,7 +67,7 @@ module Hobo
67
67
  accepts.xml do
68
68
  headers["Status"] = "Unauthorized"
69
69
  headers["WWW-Authenticate"] = %(Basic realm="Web Password")
70
- render :text => "Could't authenticate you", :status => '401 Unauthorized'
70
+ render :text => "Couldn't authenticate you", :status => '401 Unauthorized'
71
71
  end
72
72
  end
73
73
  false
data/lib/hobo/dryml.rb CHANGED
@@ -41,6 +41,16 @@ module Hobo
41
41
  DrymlGenerator.enable
42
42
  end
43
43
 
44
+
45
+ def precompile_taglibs
46
+ Dir.chdir(RAILS_ROOT) do
47
+ taglibs = Dir["vendor/plugins/**/taglibs/**/*.dryml"] + Dir["app/views/taglibs/**/*.dryml"]
48
+ taglibs.each do |f|
49
+ Hobo::Dryml::Taglib.get(:template_dir => File.dirname(f), :src => File.basename(f).remove(".dryml"))
50
+ end
51
+ end
52
+ end
53
+
44
54
 
45
55
  def clear_cache
46
56
  @renderer_classes = {}
@@ -9,16 +9,14 @@ module Hobo
9
9
  class << self
10
10
 
11
11
  def get(options)
12
- cache_key = options.inspect
13
- taglib = @cache[cache_key]
12
+ src_file = taglib_filename(options)
13
+ taglib = @cache[src_file]
14
14
  if taglib
15
15
  taglib.reload
16
16
  else
17
- src_file = taglib_filename(options)
18
17
  bundle = options[:bundle] && Bundle.bundles[options[:bundle]]
19
-
20
18
  taglib = Taglib.new(src_file, bundle)
21
- @cache[cache_key] = taglib
19
+ @cache[src_file] = taglib
22
20
  end
23
21
  taglib
24
22
  end
@@ -162,9 +162,10 @@ module Hobo
162
162
  # TODO: Calls to respond_to? in here can cause the full collection hiding behind a scoped collection to get loaded
163
163
  res = []
164
164
  empty = true
165
- scope.new_scope(:repeat_collection => enum, :even_odd => 'odd') do
165
+ scope.new_scope(:repeat_collection => enum, :even_odd => 'odd', :repeat_item => nil) do
166
166
  if enum.respond_to?(:each_pair)
167
167
  enum.each_pair do |key, value|
168
+ scope.repeat_item = value
168
169
  empty = false;
169
170
  self.this_key = key;
170
171
  new_object_context(value) { res << yield }
@@ -173,6 +174,7 @@ module Hobo
173
174
  else
174
175
  index = 0
175
176
  enum.each do |e|
177
+ scope.repeat_item = e
176
178
  empty = false;
177
179
  if enum == this
178
180
  new_field_context(index, e) { res << yield }
@@ -16,6 +16,7 @@ module Hobo
16
16
  def validate_included_in_save
17
17
  if included_in_save
18
18
  included_in_save._?.each_pair do |association, records|
19
+ next if self.class.reflections[association].options[:validate]==false
19
20
  added = false
20
21
  records.each do |record|
21
22
  # we want to call valid? on each one, but only add the error to self once
@@ -1,5 +1,5 @@
1
1
  # We need to be able to eval an expression outside of the Hobo module
2
- # so that, e.g. "Userd" doesn't eval to "Hobo::User"
2
+ # so that, e.g. "User" doesn't eval to "Hobo::User"
3
3
  # (Ruby determines this constant lookup scope lexically)
4
4
  def __top_level_eval__(obj, expr)
5
5
  obj.instance_eval(expr)
@@ -124,7 +124,11 @@ module Hobo
124
124
  def get_state(record, state)
125
125
  case state
126
126
  when Proc
127
- state.call(record)
127
+ if state.arity == 1
128
+ state.call(record)
129
+ else
130
+ record.instance_eval(&state)
131
+ end
128
132
  when String
129
133
  eval(state, record.instance_eval { binding })
130
134
  else
data/lib/hobo/model.rb CHANGED
@@ -490,7 +490,7 @@ module Hobo
490
490
  if parts[0..2].include?(0)
491
491
  nil
492
492
  else
493
- Time.local(*parts)
493
+ Time.zone ? Time.zone.local(*parts) : Time.local(*parts)
494
494
  end
495
495
  else
496
496
  value
@@ -111,7 +111,7 @@ module Hobo
111
111
  self.this = find_instance(opts)
112
112
  raise Hobo::PermissionDeniedError unless @this.method_callable_by?(current_user, method)
113
113
  if got_block
114
- instance_eval(&block)
114
+ this.with_acting_user(current_user) { instance_eval(&block) }
115
115
  else
116
116
  @this.send(method)
117
117
  end
@@ -355,9 +355,18 @@ module Hobo
355
355
 
356
356
  def re_render_form(default_action=nil)
357
357
  if params[:page_path]
358
+ @invalid_record = this
358
359
  controller, view = Controller.controller_and_view_for(params[:page_path])
359
360
  view = default_action if view == Dryml::EMPTY_PAGE
360
- render :template => "#{controller}/#{view}"
361
+
362
+ # Hack fix for Bug 477. See also bug 489.
363
+ if self.class.name == "#{controller.camelize}Controller" && view == "index"
364
+ params['action'] = 'index'
365
+ self.action_name = 'index'
366
+ index
367
+ else
368
+ render :template => "#{controller}/#{view}"
369
+ end
361
370
  else
362
371
  render :action => default_action
363
372
  end
@@ -499,7 +508,7 @@ module Hobo
499
508
  options = args.extract_options!
500
509
  self.this ||= args.first || new_for_create
501
510
  this.user_update_attributes(current_user, options[:attributes] || attribute_parameters || {})
502
- create_response(:new, &b)
511
+ create_response(:new, options, &b)
503
512
  end
504
513
 
505
514
 
@@ -508,7 +517,7 @@ module Hobo
508
517
  owner, association = find_owner_and_association(owner)
509
518
  self.this ||= args.first || association.new
510
519
  this.user_update_attributes(current_user, options[:attributes] || attribute_parameters || {})
511
- create_response(:"new_for_#{owner}", &b)
520
+ create_response(:"new_for_#{owner}", options, &b)
512
521
  end
513
522
 
514
523
 
@@ -529,10 +538,13 @@ module Hobo
529
538
  t
530
539
  end
531
540
 
541
+ def flash_notice(message)
542
+ flash[:notice] = message unless request.xhr?
543
+ end
532
544
 
533
545
 
534
546
  def create_response(new_action, options={}, &b)
535
- flash[:notice] = "The #{@this.class.name.titleize.downcase} was created successfully" if !request.xhr? && valid?
547
+ flash_notice "The #{@this.class.name.titleize.downcase} was created successfully" if valid?
536
548
 
537
549
  response_block(&b) or
538
550
  if valid?
@@ -567,7 +579,7 @@ module Hobo
567
579
 
568
580
 
569
581
  def update_response(in_place_edit_field=nil, options={}, &b)
570
- flash[:notice] = "Changes to the #{@this.class.name.titleize.downcase} were saved" if !request.xhr? && valid?
582
+ flash_notice "Changes to the #{@this.class.name.titleize.downcase} were saved" if valid?
571
583
 
572
584
  response_block(&b) or
573
585
  if valid?
@@ -603,8 +615,8 @@ module Hobo
603
615
  options = args.extract_options!
604
616
  self.this ||= args.first || find_instance
605
617
  this.user_destroy(current_user)
606
- flash[:notice] = "The #{model.name.titleize.downcase} was deleted" unless request.xhr?
607
- destroy_response(&b)
618
+ flash_notice "The #{model.name.titleize.downcase} was deleted"
619
+ destroy_response(options, &b)
608
620
  end
609
621
 
610
622
 
@@ -619,8 +631,8 @@ module Hobo
619
631
 
620
632
  # --- Lifecycle Actions --- #
621
633
 
622
- def creator_page_action(name, &b)
623
- self.this = model.new
634
+ def creator_page_action(name, options={}, &b)
635
+ self.this ||= model.new
624
636
  this.exempt_from_edit_checks = true
625
637
  @creator = model::Lifecycle.creator(name)
626
638
  raise Hobo::PermissionDeniedError unless @creator.allowed?(current_user)
@@ -693,8 +705,17 @@ module Hobo
693
705
  def hobo_completions(attribute, finder, options={})
694
706
  options = options.reverse_merge(:limit => 10, :param => :query, :query_scope => "#{attribute}_contains")
695
707
  finder = finder.limit(options[:limit]) unless finder.send(:scope, :find, :limit)
696
- finder = finder.send(options[:query_scope], params[options[:param]])
697
- items = finder.find(:all).select { |r| r.viewable_by?(current_user) }
708
+
709
+ begin
710
+ finder = finder.send(options[:query_scope], params[options[:param]])
711
+ items = finder.find(:all).select { |r| r.viewable_by?(current_user) }
712
+ rescue TypeError # must be a list of methods instead
713
+ items = []
714
+ options[:query_scope].each do |qscope|
715
+ finder2 = finder.send(qscope, params[options[:param]])
716
+ items += finder2.find(:all).select { |r| r.viewable_by?(current_user) }
717
+ end
718
+ end
698
719
  render :text => "<ul>\n" + items.map {|i| "<li>#{i.send(attribute)}</li>\n"}.join + "</ul>"
699
720
  end
700
721
 
@@ -717,6 +738,7 @@ module Hobo
717
738
 
718
739
  def permission_denied(error)
719
740
  self.this = true # Otherwise this gets sent user_view
741
+ logger.info "Hobo: Permission Denied!"
720
742
  @permission_error = error
721
743
  if "permission_denied".in?(self.class.superclass.instance_methods)
722
744
  super
@@ -330,7 +330,7 @@ module Hobo
330
330
  end
331
331
 
332
332
 
333
- # Add some singleton methods to +record+ so give the effect that +attribute+ is unknown. That is,
333
+ # Add some singleton methods to +record+ to give the effect that +attribute+ is unknown. That is,
334
334
  # attempts to access the attribute will result in a Hobo::UndefinedAccessError
335
335
  def unknownify_attribute(attr)
336
336
  metaclass.class_eval do
@@ -146,4 +146,7 @@ module Hobo::RapidHelper
146
146
  fields
147
147
  end
148
148
 
149
+ def current_time
150
+ Time.zone ? Time.zone.now : Time.now
151
+ end
149
152
  end
@@ -7,9 +7,15 @@ module Hobo
7
7
  AssociationProxyExtensions = classy_module do
8
8
 
9
9
  def scope_conditions(reflection)
10
- scope_name = reflection.options[:scope] and
11
- target_class = reflection.klass and
10
+ scope_name = reflection.options[:scope]
11
+ return nil if scope_name.nil?
12
+ target_class = reflection.klass
13
+ return nil if target_class.nil?
14
+ if scope_name.respond_to? :map
15
+ combine_conditions(*(scope_name.map {|scope| target_class.send(*scope).scope(:find)[:conditions]}))
16
+ else
12
17
  target_class.send(scope_name).scope(:find)[:conditions]
18
+ end
13
19
  end
14
20
 
15
21
 
@@ -178,21 +178,21 @@ module Hobo
178
178
  def_scope :conditions => ["#{column_sql(col)} <> ?", true]
179
179
 
180
180
  # published_before(time)
181
- elsif name =~ /^(.*)_before$/ && (col = column("#{$1}_at")) && col.type.in?([:date, :datetime, :time, :timestamp])
181
+ elsif name =~ /^(.*)_before$/ && (col = column("#{$1}_at") || column("#{$1}_date") || column("#{$1}_on")) && col.type.in?([:date, :datetime, :time, :timestamp])
182
182
 
183
183
  def_scope do |time|
184
184
  { :conditions => ["#{column_sql(col)} < ?", time] }
185
185
  end
186
186
 
187
187
  # published_after(time)
188
- elsif name =~ /^(.*)_after$/ && (col = column("#{$1}_at")) && col.type.in?([:date, :datetime, :time, :timestamp])
188
+ elsif name =~ /^(.*)_after$/ && (col = column("#{$1}_at") || column("#{$1}_date") || column("#{$1}_on")) && col.type.in?([:date, :datetime, :time, :timestamp])
189
189
 
190
190
  def_scope do |time|
191
191
  { :conditions => ["#{column_sql(col)} > ?", time] }
192
192
  end
193
193
 
194
194
  # published_between(time1, time2)
195
- elsif name =~ /^(.*)_between$/ && (col = column("#{$1}_at")) && col.type.in?([:date, :datetime, :time, :timestamp])
195
+ elsif name =~ /^(.*)_between$/ && (col = column("#{$1}_at") || column("#{$1}_date") || column("#{$1}_on")) && col.type.in?([:date, :datetime, :time, :timestamp])
196
196
 
197
197
  def_scope do |time1, time2|
198
198
  { :conditions => ["#{column_sql(col)} >= ? AND #{column_sql(col)} <= ?", time1, time2] }
@@ -13,7 +13,8 @@ module Hobo
13
13
  end
14
14
 
15
15
  filter_parameter_logging "password"
16
- skip_before_filter :login_required, :only => [:login, :signup, :forgot_password, :reset_password_page, :reset_password]
16
+ skip_before_filter :login_required, :only => [:login, :signup, :forgot_password, :reset_password, :do_reset_password,
17
+ :accept_invitation, :do_accept_invitation]
17
18
 
18
19
  include_taglib "rapid_user_pages", :plugin => "hobo"
19
20
 
@@ -26,7 +26,7 @@ class HoboGenerator < Rails::Generator::Base
26
26
  m.file "initializer.rb", File.join("config/initializers/hobo.rb")
27
27
  end
28
28
  end
29
-
29
+
30
30
  protected
31
31
  def banner
32
32
  "Usage: #{$0} #{spec.name} [--add-routes] [--add-gem]"
@@ -1,3 +1 @@
1
1
  <def tag="app-name"><%= File.basename(Dir.chdir(RAILS_ROOT) { Dir.getwd }).strip.titleize %></def>
2
-
3
-
@@ -0,0 +1,45 @@
1
+ require 'fileutils'
2
+
3
+ class HoboAdminSiteGenerator < HoboSubsiteGenerator
4
+
5
+ default_options :rapid => true
6
+
7
+ def manifest
8
+ m = super
9
+
10
+ m.template "admin.css", File.join('public/stylesheets/admin.css')
11
+ if invite_only?
12
+ m.dependency "hobo_model_controller", ["admin/user"]
13
+ m.template "users_index.dryml", "app/views/admin/users/index.dryml"
14
+ end
15
+ m
16
+ end
17
+
18
+ def invite_only?
19
+ options[:invite_only]
20
+ end
21
+
22
+ protected
23
+
24
+ def banner
25
+ "Usage: #{$0} #{spec.name} [--make-front-site | --no-front-site] [--invite-only]"
26
+ end
27
+
28
+ def add_options!(opt)
29
+ opt.separator ''
30
+ opt.separator 'Options:'
31
+ opt.on("--make-front-site", "rename application.dryml to front_site.dryml") do |v|
32
+ options[:make_front_site] = true
33
+ end
34
+ opt.on("--no-front-site", "do not rename application.dryml to front_site.dryml ") do |v|
35
+ options[:make_front_site] = false
36
+ end
37
+ opt.on("--no-rapid", "don't include Rapid features in the subsite taglib") do |v|
38
+ options[:rapid] = false
39
+ end
40
+ opt.on("--invite-only", "Add features for an invite only website") do |v|
41
+ options[:invite_only] = true
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,2 @@
1
+ html, body { background: #333;}
2
+ .page-header { background: #555; }
@@ -0,0 +1 @@
1
+ <!-- Global taglib - these tags are shared across all subsites -->
@@ -0,0 +1,13 @@
1
+ class <%= subsite_name %>::<%= subsite_name %>SiteController < ApplicationController
2
+
3
+ hobo_controller
4
+
5
+ before_filter :admin_required
6
+
7
+ private
8
+
9
+ def admin_required
10
+ redirect_to home_page unless current_user.administrator?
11
+ end
12
+
13
+ end
@@ -0,0 +1,32 @@
1
+ <!-- Tag definitions for the <%= subsite_name %> subsite -->
2
+
3
+ <% if options[:rapid] -%>
4
+ <include src="rapid" plugin="hobo"/>
5
+
6
+ <include src="taglibs/auto/<%= file_name %>/rapid/cards"/>
7
+ <include src="taglibs/auto/<%= file_name %>/rapid/pages"/>
8
+ <include src="taglibs/auto/<%= file_name %>/rapid/forms"/>
9
+
10
+ <set-theme name="clean"/>
11
+ <% end -%>
12
+
13
+ <def tag="app-name"><%= app_name %></def>
14
+
15
+ <extend tag="page">
16
+ <old-page merge>
17
+ <append-stylesheets:>
18
+ <stylesheet name="admin"/>
19
+ </append-stylesheets:>
20
+ <footer:>
21
+ <a href="#{base_url}/">View Site</a>
22
+ </footer:>
23
+ </old-page>
24
+ </extend>
25
+ <% if invite_only? -%>
26
+
27
+ <extend tag="card" for="User">
28
+ <old-card merge>
29
+ <append-header:><%%= h this.state.titleize %></append-header:>
30
+ </old-card>
31
+ </extend>
32
+ <% end -%>
@@ -0,0 +1,5 @@
1
+ <index-page>
2
+ <after-count:>
3
+ <a with="&User" action="invite">Invite a new user</a>
4
+ </after-count:>
5
+ </index-page>
@@ -62,10 +62,14 @@ class HoboFrontControllerGenerator < Rails::Generator::NamedBase
62
62
  return unless File.exists?(index_path)
63
63
  File.unlink(index_path)
64
64
  end
65
+
66
+ def invite_only?
67
+ options[:invite_only]
68
+ end
65
69
 
66
70
  protected
67
71
  def banner
68
- "Usage: #{$0} #{spec.name} <controller-name> [--add-routes] [--delete-index]"
72
+ "Usage: #{$0} #{spec.name} <controller-name> [--add-routes] [--delete-index] [--invite-only]"
69
73
  end
70
74
 
71
75
  def add_options!(opt)
@@ -75,6 +79,8 @@ class HoboFrontControllerGenerator < Rails::Generator::NamedBase
75
79
  "Modify config/routes.rb to support the front controller") { |v| options[:add_routes] = true }
76
80
  opt.on("--delete-index",
77
81
  "Delete public/index.html") { |v| options[:delete_index] = true }
82
+ opt.on("--invite-only",
83
+ "Add features for an invite only website") { |v| options[:invite_only] = true }
78
84
  end
79
85
 
80
86
  end