hobo 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/CHANGES.txt +330 -0
  2. data/Manifest +12 -4
  3. data/Rakefile +4 -6
  4. data/dryml_generators/rapid/cards.dryml.erb +5 -1
  5. data/dryml_generators/rapid/forms.dryml.erb +8 -10
  6. data/dryml_generators/rapid/pages.dryml.erb +65 -36
  7. data/hobo.gemspec +28 -15
  8. data/lib/active_record/association_collection.rb +3 -22
  9. data/lib/hobo.rb +25 -258
  10. data/lib/hobo/accessible_associations.rb +131 -0
  11. data/lib/hobo/authentication_support.rb +15 -9
  12. data/lib/hobo/composite_model.rb +1 -1
  13. data/lib/hobo/controller.rb +7 -8
  14. data/lib/hobo/dryml.rb +9 -10
  15. data/lib/hobo/dryml/dryml_builder.rb +7 -1
  16. data/lib/hobo/dryml/dryml_doc.rb +161 -0
  17. data/lib/hobo/dryml/dryml_generator.rb +18 -9
  18. data/lib/hobo/dryml/part_context.rb +76 -42
  19. data/lib/hobo/dryml/tag_parameters.rb +1 -0
  20. data/lib/hobo/dryml/taglib.rb +2 -1
  21. data/lib/hobo/dryml/template.rb +39 -29
  22. data/lib/hobo/dryml/template_environment.rb +79 -37
  23. data/lib/hobo/dryml/template_handler.rb +66 -21
  24. data/lib/hobo/guest.rb +2 -10
  25. data/lib/hobo/hobo_helper.rb +125 -53
  26. data/lib/hobo/include_in_save.rb +0 -1
  27. data/lib/hobo/lifecycles.rb +54 -24
  28. data/lib/hobo/lifecycles/actions.rb +95 -31
  29. data/lib/hobo/lifecycles/creator.rb +18 -23
  30. data/lib/hobo/lifecycles/lifecycle.rb +86 -62
  31. data/lib/hobo/lifecycles/state.rb +1 -2
  32. data/lib/hobo/lifecycles/transition.rb +22 -28
  33. data/lib/hobo/model.rb +64 -176
  34. data/lib/hobo/model_controller.rb +67 -54
  35. data/lib/hobo/model_router.rb +5 -2
  36. data/lib/hobo/permissions.rb +397 -0
  37. data/lib/hobo/permissions/associations.rb +167 -0
  38. data/lib/hobo/scopes.rb +15 -38
  39. data/lib/hobo/scopes/association_proxy_extensions.rb +15 -5
  40. data/lib/hobo/scopes/automatic_scopes.rb +43 -18
  41. data/lib/hobo/scopes/named_scope_extensions.rb +2 -2
  42. data/lib/hobo/user.rb +10 -4
  43. data/lib/hobo/user_controller.rb +6 -5
  44. data/lib/hobo/view_hints.rb +58 -0
  45. data/rails_generators/hobo/hobo_generator.rb +7 -3
  46. data/rails_generators/hobo/templates/guest.rb +1 -13
  47. data/rails_generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
  48. data/rails_generators/hobo_model/hobo_model_generator.rb +4 -2
  49. data/rails_generators/hobo_model/templates/hints.rb +4 -0
  50. data/rails_generators/hobo_model/templates/model.rb +8 -8
  51. data/rails_generators/hobo_model_controller/hobo_model_controller_generator.rb +10 -0
  52. data/rails_generators/hobo_model_controller/templates/controller.rb +1 -1
  53. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +91 -56
  54. data/rails_generators/hobo_rapid/templates/lowpro.js +15 -15
  55. data/rails_generators/hobo_rapid/templates/reset.css +36 -3
  56. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +13 -17
  57. data/rails_generators/hobo_user_controller/templates/controller.rb +1 -1
  58. data/rails_generators/hobo_user_model/templates/model.rb +18 -16
  59. data/taglibs/core.dryml +60 -18
  60. data/taglibs/rapid.dryml +8 -401
  61. data/taglibs/rapid_core.dryml +586 -0
  62. data/taglibs/rapid_document_tags.dryml +28 -10
  63. data/taglibs/rapid_editing.dryml +92 -55
  64. data/taglibs/rapid_forms.dryml +406 -87
  65. data/taglibs/rapid_generics.dryml +1 -1
  66. data/taglibs/rapid_navigation.dryml +2 -1
  67. data/taglibs/rapid_pages.dryml +7 -16
  68. data/taglibs/rapid_plus.dryml +39 -14
  69. data/taglibs/rapid_support.dryml +1 -1
  70. data/taglibs/rapid_user_pages.dryml +14 -4
  71. data/tasks/{generate_tag_reference.rb → generate_tag_reference.rake} +49 -18
  72. data/tasks/hobo_tasks.rake +16 -0
  73. data/test/permissions/models/models.rb +134 -0
  74. data/test/permissions/models/schema.rb +55 -0
  75. data/test/permissions/models/test.sqlite3 +0 -0
  76. data/test/permissions/test_permissions.rb +436 -0
  77. metadata +27 -14
  78. data/lib/hobo/mass_assignment.rb +0 -64
  79. data/rails_generators/hobo/templates/patch_routing.rb +0 -30
  80. data/uninstall.rb +0 -1
@@ -90,21 +90,27 @@ module Hobo
90
90
  # When called with before_filter :login_from_cookie will check for an :auth_token
91
91
  # cookie and log the user back in if apropriate
92
92
  def login_from_cookie
93
- return unless (token = cookies[:auth_token]) && !logged_in?
94
-
95
- user_model = token[:user_model].constantize
96
- user = user_model.find_by_remember_token(token)
97
- if user && user.remember_token?
93
+ if (user = authenticated_user_from_cookie)
98
94
  user.remember_me
99
- current_user = user
95
+ self.current_user = user
100
96
  create_auth_cookie
101
97
  end
102
98
  end
99
+
100
+
101
+ def authenticated_user_from_cookie
102
+ !logged_in? and
103
+ cookie = cookies[:auth_token] and
104
+ (token, model_name = cookie.split) and
105
+ user_model = model_name.constantize rescue nil and
106
+ user = user_model.find_by_remember_token(token) and
107
+ user.remember_token? and
108
+ user
109
+ end
103
110
 
104
111
  def create_auth_cookie
105
- cookies[:auth_token] = { :value => current_user.remember_token ,
106
- :expires => current_user.remember_token_expires_at,
107
- :user_model => current_user.model }
112
+ cookies[:auth_token] = { :value => "#{current_user.remember_token} #{current_user.class.name}",
113
+ :expires => current_user.remember_token_expires_at }
108
114
  end
109
115
 
110
116
  private
@@ -56,7 +56,7 @@ module Hobo
56
56
 
57
57
 
58
58
  def typed_id
59
- "#{self.class.name.underscore}_#{id}"
59
+ "#{self.class.name.underscore}:#{id}"
60
60
  end
61
61
 
62
62
 
@@ -15,6 +15,7 @@ module Hobo
15
15
  def included_in_class(klass)
16
16
  klass.extend(ClassMethods)
17
17
  klass.class_eval do
18
+ before_filter :login_from_cookie
18
19
  alias_method_chain :redirect_to, :object_url
19
20
  around_filter do |controller, action|
20
21
  Thread.current['Hobo::current_controller'] = controller
@@ -66,11 +67,10 @@ module Hobo
66
67
 
67
68
  def hobo_ajax_response(*args)
68
69
  results = args.extract_options!
69
- this = args.first || @this
70
70
  page_path = params[:page_path]
71
71
  r = params[:render]
72
72
  if r
73
- ajax_update_response(this, page_path, r.values, results)
73
+ ajax_update_response(page_path, r.values, results)
74
74
  true
75
75
  else
76
76
  false
@@ -78,8 +78,8 @@ module Hobo
78
78
  end
79
79
 
80
80
 
81
- def ajax_update_response(this, page_path, render_specs, results={})
82
- add_variables_to_assigns
81
+ def ajax_update_response(page_path, render_specs, results={})
82
+ @template.send(:_evaluate_assigns_and_ivars)
83
83
  renderer = Hobo::Dryml.page_renderer(@template, [], page_path) if page_path
84
84
 
85
85
  render :update do |page|
@@ -89,12 +89,11 @@ module Hobo
89
89
  dom_id = spec[:id]
90
90
 
91
91
  if spec[:part_context]
92
- part_name, part_this, locals = Dryml::PartContext.unmarshal(spec[:part_context], this, session)
93
- part_content = renderer.call_part(dom_id, part_name, part_this, *locals)
92
+ part_content = renderer.refresh_part(spec[:part_context], session, dom_id)
94
93
  page.call(function, dom_id, part_content)
95
94
  elsif spec[:result]
96
95
  result = results[spec[:result].to_sym]
97
- page.call(function, spec[:id], result)
96
+ page.call(function, dom_id, result)
98
97
  else
99
98
  # spec didn't specify any action :-/
100
99
  end
@@ -168,7 +167,7 @@ end
168
167
  class ActionController::Base
169
168
 
170
169
  def home_page
171
- ""
170
+ base_url
172
171
  end
173
172
 
174
173
  end
@@ -58,12 +58,12 @@ module Hobo
58
58
  end
59
59
 
60
60
 
61
- def page_renderer_for_template(view, template)
62
- page_renderer(view, template.locals.keys, template.path_without_extension)
61
+ def page_renderer_for_template(view, local_names, template)
62
+ page_renderer(view, local_names, template.path_without_extension, template.filename)
63
63
  end
64
64
 
65
65
 
66
- def page_renderer(view, local_names=[], page=nil)
66
+ def page_renderer(view, local_names=[], page=nil, filename=nil)
67
67
  if RAILS_ENV == "development"
68
68
  clear_cache
69
69
  Taglib.clear_cache
@@ -79,16 +79,15 @@ module Hobo
79
79
  make_renderer_class("", page, local_names, DEFAULT_IMPORTS, included_taglibs)
80
80
  @tag_page_renderer_classes[controller_class.name].new(page, view)
81
81
  else
82
- template_path = view.finder.pick_template(page, "dryml")
83
-
84
- mtime = File.mtime(template_path)
82
+ filename ||= view._pick_template(page + ".dryml").filename
83
+ mtime = File.mtime(filename)
85
84
  renderer_class = @renderer_classes[page]
86
85
 
87
86
  # do we need to recompile?
88
- if (!renderer_class or # nothing cached?
89
- (local_names - renderer_class.compiled_local_names).any? or # any new local names?
87
+ if (!renderer_class || # nothing cached?
88
+ (local_names - renderer_class.compiled_local_names).any? || # any new local names?
90
89
  renderer_class.load_time < mtime) # cache out of date?
91
- renderer_class = make_renderer_class(File.read(template_path), template_path, local_names,
90
+ renderer_class = make_renderer_class(File.read(filename), filename, local_names,
92
91
  DEFAULT_IMPORTS, included_taglibs)
93
92
  renderer_class.load_time = mtime
94
93
  @renderer_classes[page] = renderer_class
@@ -106,7 +105,7 @@ module Hobo
106
105
 
107
106
 
108
107
  def controller_taglibs(controller_class)
109
- (controller_class.respond_to?(:included_taglibs) && controller_class.included_taglibs) || []
108
+ controller_class.try.included_taglibs || []
110
109
  end
111
110
 
112
111
 
@@ -62,7 +62,13 @@ module Hobo::Dryml
62
62
  # Strip off "_erbout = ''" from the beginning and "; _erbout"
63
63
  # from the end, because we do things differently around
64
64
  # here. (_erbout is defined as a method)
65
- ERB.new(erb_src, nil, ActionView::Base.erb_trim_mode).src[("_erbout = '';").length..-("; _erbout".length)]
65
+ trim_mode = if defined?(ActionView::TemplateHandlers::ERB.erb_trim_mode)
66
+ ActionView::TemplateHandlers::ERB.erb_trim_mode
67
+ else
68
+ ActionView::Base.erb_trim_mode
69
+ end
70
+
71
+ ERB.new(erb_src, nil, trim_mode).src[("_erbout = '';").length..-("; _erbout".length)]
66
72
  end
67
73
 
68
74
 
@@ -0,0 +1,161 @@
1
+ require 'rexml/xpath'
2
+
3
+ module Hobo
4
+
5
+ module Dryml
6
+
7
+ # DrymlDoc provides the facility to parse a directory tree of DRYML taglibs, building a collection of objects that provide metadata
8
+ module DrymlDoc
9
+
10
+ def self.load_taglibs(directory, taglib_class=DrymlDoc::Taglib)
11
+ dryml_files = Dir["#{directory}/**/*.dryml"]
12
+
13
+ dryml_files.map { |f| taglib_class.new(directory, f) }
14
+ end
15
+
16
+ class Taglib
17
+
18
+ def initialize(home, filename)
19
+ @name = filename.sub(/.dryml$/, '')[home.length+1..-1]
20
+ @doc = Hobo::Dryml::Parser::Document.new(File.read(filename), filename)
21
+ parse_tag_defs
22
+ end
23
+
24
+ attr_reader :name, :doc, :tag_defs
25
+
26
+ def comment
27
+ first_node = doc[0][0]
28
+ doc.restore_erb_scriptlets(first_node.to_s.strip) if first_node.is_a?(REXML::Comment)
29
+ end
30
+
31
+ def comment_html
32
+ Maruku.new(comment).to_html
33
+ end
34
+
35
+
36
+ private
37
+
38
+ def tagdef_class
39
+ self.class.parent.const_get('TagDef')
40
+ end
41
+
42
+ def parse_tag_defs
43
+ @tag_defs = []
44
+ REXML::XPath.match(doc, '/*/*[@tag]').each { |node| @tag_defs << tagdef_class.new(self, node) }
45
+ end
46
+
47
+ end
48
+
49
+ class TagDef
50
+
51
+ def initialize(taglib, node)
52
+ @taglib = taglib
53
+ @node = node
54
+ end
55
+
56
+ attr_reader :taglib, :node
57
+ delegate :doc, :to => :taglib
58
+
59
+
60
+ def name
61
+ node.attributes['tag']
62
+ end
63
+
64
+ def source
65
+ doc.restore_erb_scriptlets(node.to_s).strip
66
+ end
67
+
68
+ # The contents of the XML comment, if any, immediately above the tag definition
69
+ def comment
70
+ @comment ||= begin
71
+ space = node.previous_sibling and
72
+ space.to_s.blank? && space.to_s.count("\n") == 1 and
73
+ comment_node = space.previous_sibling
74
+
75
+ if comment_node.is_a?(REXML::Comment)
76
+ doc.restore_erb_scriptlets(comment_node.to_s.strip)
77
+ end
78
+ end
79
+ end
80
+
81
+
82
+ def comment_intro
83
+ comment && comment =~ /(.*?)^#/m ? $1 : comment
84
+ end
85
+
86
+
87
+ def comment_rest
88
+ comment && comment[comment_intro.length..-1]
89
+ end
90
+
91
+ %w(comment comment_intro comment_rest).each do |m|
92
+ class_eval "def #{m}_html; Maruku.new(#{m}).to_html.gsub(/&amp;/, '&'); end"
93
+ end
94
+
95
+ def comment_html
96
+
97
+ end
98
+
99
+ def no_doc?
100
+ comment =~ /^nodoc\b/
101
+ end
102
+
103
+ # An array of the arrtibute names defined by this tag
104
+ def attributes
105
+ (node.attributes['attrs'] || "").split(/\s*,\s*/).where_not.blank?
106
+ end
107
+
108
+
109
+ # Returns a recursive array srtucture, where each item in the array is a pair: [parameter_name, sub_parameters]
110
+ # (sub-parameters is the same kind of structure)
111
+ def parameters(element=node)
112
+ result = []
113
+ element.elements.each do |e|
114
+ if (p = e.attributes['param'])
115
+ param_name = p == "&true" ? e.name : p
116
+ result << [param_name, parameters(e)]
117
+ else
118
+ result.concat(parameters(e))
119
+ end
120
+ end
121
+ result
122
+ end
123
+
124
+
125
+ # Is this the base definition of a polymorphic tag
126
+ def polymorphic?
127
+ node.attributes['polymorphic'].present?
128
+ end
129
+
130
+ # Is this an <extend>?
131
+ def extension?
132
+ node.name == "extend"
133
+ end
134
+
135
+
136
+ # The definition's 'for' attribute
137
+ def for_type
138
+ node.attributes['for']
139
+ end
140
+
141
+
142
+ # The name of the tag, if any, that this definition merges its parameters into
143
+ # That is, the tag with 'merge' or 'merge-params' declared
144
+ def merge_params
145
+ REXML::XPath.first(node, ".//*[@merge|@merge-params]")._?.name
146
+ end
147
+
148
+ # The name of the tag, if any, that this definition merges its attributes into
149
+ # That is, the tag with 'merge' or 'merge-attrs' declared
150
+ def merge_attrs
151
+ REXML::XPath.first(node, ".//*[@merge|@merge-attrs]")._?.name
152
+ end
153
+
154
+ end
155
+
156
+
157
+ end
158
+
159
+ end
160
+
161
+ end
@@ -13,6 +13,7 @@ module Hobo
13
13
  HEADER = "<!-- AUTOMATICALLY GENERATED FILE - DO NOT EDIT -->\n\n"
14
14
 
15
15
  def self.run
16
+ return if RAILS_ENV == "production"
16
17
  @generator ||= DrymlGenerator.new
17
18
  @generator.run
18
19
  end
@@ -137,15 +138,6 @@ module Hobo
137
138
  end
138
139
 
139
140
 
140
- def primary_collection_name(klass=model)
141
- dependent_collection_names = klass.reflections.values.select do |refl|
142
- refl.macro == :has_many && refl.options[:dependent]
143
- end.*.name
144
-
145
- (dependent_collection_names - through_collection_names(klass)).first
146
- end
147
-
148
-
149
141
  def through_collection_names(klass=model)
150
142
  klass.reflections.values.select do |refl|
151
143
  refl.macro == :has_many && refl.options[:through]
@@ -199,6 +191,23 @@ module Hobo
199
191
  end
200
192
 
201
193
 
194
+ def creators
195
+ defined?(model::Lifecycle) ? model::Lifecycle.publishable_creators : []
196
+ end
197
+
198
+ def transitions
199
+ defined?(model::Lifecycle) ? model::Lifecycle.publishable_transitions : []
200
+ end
201
+
202
+ def creator_names
203
+ creators.map { |c| c.name.to_s }
204
+ end
205
+
206
+ def transition_names
207
+ transitions.map { |t| t.name.to_s }.uniq
208
+ end
209
+
210
+
202
211
  def a_or_an(word)
203
212
  (word =~ /^[aeiou]/i ? "an " : "a ") + word
204
213
  end
@@ -7,7 +7,7 @@ module Hobo
7
7
 
8
8
  class TamperedWithPartContext < StandardError; end
9
9
 
10
- class Id < String; end
10
+ class TypedId < String; end
11
11
 
12
12
  class << self
13
13
  attr_accessor :secret, :digest
@@ -23,75 +23,109 @@ module Hobo
23
23
  "hoboParts['#{dom_id}'] = (#{code});\n"
24
24
  end.join
25
25
  end
26
-
27
-
28
- def initialize(part_name, this_id, locals)
29
- @part_name = part_name
30
- @this_id = this_id
31
- @locals = locals.map { |l| pre_marshal(l) }
32
- end
33
-
34
-
35
- def pre_marshal(x)
26
+
27
+
28
+ def self.pre_marshal(x)
36
29
  if x.is_a?(ActiveRecord::Base) && x.respond_to?(:typed_id)
37
- Id.new(x.typed_id)
30
+ TypedId.new(x.typed_id)
38
31
  else
39
32
  x
40
33
  end
41
34
  end
42
35
 
43
36
 
37
+ def self.for_call(part_name, environment, locals)
38
+ new do |c|
39
+ c.part_name = part_name
40
+ c.locals = locals.map { |l| pre_marshal(l) }
41
+ c.this_id = environment.typed_id
42
+ c.form_field_path = environment.form_field_path
43
+ end
44
+ end
45
+
46
+
47
+ def self.for_refresh(encoded_context, page_this, session)
48
+ new do |c|
49
+ c.unmarshal(encoded_context, page_this, session)
50
+ end
51
+ end
52
+
53
+
54
+ def initialize
55
+ yield self
56
+ end
57
+
58
+ attr_accessor :part_name, :locals, :this, :this_field, :this_id, :form_field_path
59
+
60
+
44
61
  def marshal(session)
45
62
  context = [@part_name, @this_id, @locals]
63
+ context << form_field_path if form_field_path
46
64
  data = Base64.encode64(Marshal.dump(context)).strip
47
- digest = self.class.generate_digest(data, session)
65
+ digest = generate_digest(data, session)
48
66
  "#{data}--#{digest}"
49
67
  end
50
68
 
51
69
 
52
- class << self
70
+ # Unmarshal part context to a hash and verify its integrity.
71
+ def unmarshal(client_store, page_this, session)
72
+ data, digest = CGI.unescape(client_store).strip.split('--')
53
73
 
54
- # Generate the HMAC keyed message digest. Uses SHA1 by default.
55
- def generate_digest(data, session)
56
- secret = self.secret || ActionController::Base.cached_session_options.first[:secret]
57
- key = secret.respond_to?(:call) ? secret.call(session) : secret
58
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key, data)
59
- end
74
+ raise TamperedWithPartContext unless digest == generate_digest(data, session)
60
75
 
76
+ context = Marshal.load(Base64.decode64(data))
77
+
78
+ part_name, this_id, locals, form_field_path = context
61
79
 
62
- # Unmarshal part context to a hash and verify its integrity.
63
- def unmarshal(client_store, this, session)
64
- data, digest = CGI.unescape(client_store).strip.split('--')
80
+ RAILS_DEFAULT_LOGGER.info "Call part: #{part_name}. this-id = #{this_id}, locals = #{locals.inspect}"
81
+ RAILS_DEFAULT_LOGGER.info " : form_field_path = #{form_field_path.inspect}" if form_field_path
82
+
83
+ self.part_name = part_name
84
+ self.this_id = this_id
85
+ self.locals = restore_locals(locals)
86
+ self.form_field_path = form_field_path
87
+
88
+ parse_this_id(page_this)
89
+ end
65
90
 
66
- raise TamperedWithPartContext unless digest == generate_digest(data, session)
67
91
 
68
- part_name, this_id, locals = Marshal.load(Base64.decode64(data))
69
- RAILS_DEFAULT_LOGGER.info("Call part: #{part_name}. this-id = #{this_id}, locals = #{locals.inspect}")
92
+ # Generate the HMAC keyed message digest. Uses SHA1 by default.
93
+ def generate_digest(data, session)
94
+ secret = self.class.secret || ActionController::Base.cached_session_options.first[:secret]
95
+ key = secret.respond_to?(:call) ? secret.call(session) : secret
96
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(self.class.digest), key, data)
97
+ end
70
98
 
71
- [part_name, restore_part_this(this_id, this), restore_locals(locals)]
72
- end
73
99
 
74
- def restore_part_this(this_id, this)
75
- if this_id == "this" or this_id.blank?
76
- this
77
- elsif this_id == "nil"
78
- nil
100
+
101
+ def parse_this_id(page_this)
102
+ if this_id == "this"
103
+ self.this = page_this
104
+ elsif this_id =~ /^this:(.*)/
105
+ self.this = page_this
106
+ self.this_field = $1
107
+ elsif this_id == "nil"
108
+ nil
109
+ else
110
+ parts = this_id.split(':')
111
+ if parts.length == 3
112
+ self.this = Hobo::Model.find_by_typed_id("#{parts[0]}:#{parts[1]}")
113
+ self.this_field = parts[2]
79
114
  else
80
- Hobo.object_from_dom_id(this_id)
115
+ self.this = Hobo::Model.find_by_typed_id(this_id)
81
116
  end
82
117
  end
118
+ end
83
119
 
84
120
 
85
- def restore_locals(locals)
86
- locals.map do |l|
87
- if l.is_a?(Id)
88
- Hobo.object_from_dom_id(l)
89
- else
90
- l
91
- end
121
+ def restore_locals(locals)
122
+ locals.map do |l|
123
+ if l.is_a?(TypedId)
124
+ Hobo::Model.find_by_typed_id(this_id)
125
+ else
126
+ l
92
127
  end
93
128
  end
94
-
95
129
  end
96
130
 
97
131
  end