hobo 0.5.3

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 (125) hide show
  1. data/LICENSE.txt +22 -0
  2. data/README.txt +18 -0
  3. data/bin/hobo +81 -0
  4. data/hobo_files/plugin/CHANGES.txt +963 -0
  5. data/hobo_files/plugin/LICENSE.txt +22 -0
  6. data/hobo_files/plugin/README +4 -0
  7. data/hobo_files/plugin/Rakefile +11 -0
  8. data/hobo_files/plugin/generators/hobo/hobo_generator.rb +37 -0
  9. data/hobo_files/plugin/generators/hobo/templates/application.dryml +2 -0
  10. data/hobo_files/plugin/generators/hobo/templates/guest.rb +31 -0
  11. data/hobo_files/plugin/generators/hobo_front_controller/USAGE +11 -0
  12. data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +90 -0
  13. data/hobo_files/plugin/generators/hobo_front_controller/templates/controller.rb +51 -0
  14. data/hobo_files/plugin/generators/hobo_front_controller/templates/functional_test.rb +18 -0
  15. data/hobo_files/plugin/generators/hobo_front_controller/templates/helper.rb +2 -0
  16. data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +43 -0
  17. data/hobo_files/plugin/generators/hobo_front_controller/templates/login.dryml +44 -0
  18. data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +18 -0
  19. data/hobo_files/plugin/generators/hobo_front_controller/templates/signup.dryml +45 -0
  20. data/hobo_files/plugin/generators/hobo_model/USAGE +26 -0
  21. data/hobo_files/plugin/generators/hobo_model/hobo_model_generator.rb +38 -0
  22. data/hobo_files/plugin/generators/hobo_model/templates/fixtures.yml +11 -0
  23. data/hobo_files/plugin/generators/hobo_model/templates/migration.rb +13 -0
  24. data/hobo_files/plugin/generators/hobo_model/templates/model.rb +24 -0
  25. data/hobo_files/plugin/generators/hobo_model/templates/unit_test.rb +10 -0
  26. data/hobo_files/plugin/generators/hobo_model_controller/USAGE +30 -0
  27. data/hobo_files/plugin/generators/hobo_model_controller/hobo_model_controller_generator.rb +43 -0
  28. data/hobo_files/plugin/generators/hobo_model_controller/templates/controller.rb +5 -0
  29. data/hobo_files/plugin/generators/hobo_model_controller/templates/functional_test.rb +18 -0
  30. data/hobo_files/plugin/generators/hobo_model_controller/templates/helper.rb +2 -0
  31. data/hobo_files/plugin/generators/hobo_model_controller/templates/view.rhtml +2 -0
  32. data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +51 -0
  33. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +436 -0
  34. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/default_mapping.rb +11 -0
  35. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/banner.gif +0 -0
  36. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_bodytop.gif +0 -0
  37. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_01.gif +0 -0
  38. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_02.gif +0 -0
  39. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_03.gif +0 -0
  40. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_04.gif +0 -0
  41. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_bottom.gif +0 -0
  42. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_left.gif +0 -0
  43. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_right.gif +0 -0
  44. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_top.gif +0 -0
  45. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_blue.gif +0 -0
  46. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_dblue.gif +0 -0
  47. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_green.gif +0 -0
  48. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_purple.gif +0 -0
  49. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_red.gif +0 -0
  50. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/logo.gif +0 -0
  51. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/spinner.gif +0 -0
  52. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_dblue.gif +0 -0
  53. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_green.gif +0 -0
  54. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_purple.gif +0 -0
  55. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_red.gif +0 -0
  56. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_01.gif +0 -0
  57. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_02.gif +0 -0
  58. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_03.gif +0 -0
  59. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_04.gif +0 -0
  60. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_bottom.gif +0 -0
  61. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_left.gif +0 -0
  62. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_right.gif +0 -0
  63. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_top.gif +0 -0
  64. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +390 -0
  65. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +104 -0
  66. data/hobo_files/plugin/generators/hobo_user_model/USAGE +26 -0
  67. data/hobo_files/plugin/generators/hobo_user_model/hobo_user_model_generator.rb +38 -0
  68. data/hobo_files/plugin/generators/hobo_user_model/templates/fixtures.yml +11 -0
  69. data/hobo_files/plugin/generators/hobo_user_model/templates/migration.rb +15 -0
  70. data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +58 -0
  71. data/hobo_files/plugin/generators/hobo_user_model/templates/unit_test.rb +10 -0
  72. data/hobo_files/plugin/init.rb +44 -0
  73. data/hobo_files/plugin/lib/action_view_extensions/base.rb +14 -0
  74. data/hobo_files/plugin/lib/active_record/has_many_association.rb +54 -0
  75. data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +22 -0
  76. data/hobo_files/plugin/lib/active_record/table_definition.rb +34 -0
  77. data/hobo_files/plugin/lib/extensions.rb +245 -0
  78. data/hobo_files/plugin/lib/extensions/test_case.rb +130 -0
  79. data/hobo_files/plugin/lib/hobo.rb +353 -0
  80. data/hobo_files/plugin/lib/hobo/HtmlString +3 -0
  81. data/hobo_files/plugin/lib/hobo/authenticated_user.rb +106 -0
  82. data/hobo_files/plugin/lib/hobo/authentication_support.rb +108 -0
  83. data/hobo_files/plugin/lib/hobo/composite_model.rb +66 -0
  84. data/hobo_files/plugin/lib/hobo/controller.rb +134 -0
  85. data/hobo_files/plugin/lib/hobo/controller_helpers.rb +135 -0
  86. data/hobo_files/plugin/lib/hobo/core.rb +475 -0
  87. data/hobo_files/plugin/lib/hobo/define_tags.rb +56 -0
  88. data/hobo_files/plugin/lib/hobo/dryml.rb +161 -0
  89. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +126 -0
  90. data/hobo_files/plugin/lib/hobo/dryml/tag_module.rb +9 -0
  91. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +57 -0
  92. data/hobo_files/plugin/lib/hobo/dryml/template.rb +586 -0
  93. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +302 -0
  94. data/hobo_files/plugin/lib/hobo/dryml/template_handler.rb +19 -0
  95. data/hobo_files/plugin/lib/hobo/generator.rb +25 -0
  96. data/hobo_files/plugin/lib/hobo/html_string.rb +3 -0
  97. data/hobo_files/plugin/lib/hobo/lazy_hash.rb +28 -0
  98. data/hobo_files/plugin/lib/hobo/mapping_tags.rb +262 -0
  99. data/hobo_files/plugin/lib/hobo/markdown_string.rb +7 -0
  100. data/hobo_files/plugin/lib/hobo/model.rb +391 -0
  101. data/hobo_files/plugin/lib/hobo/model_controller.rb +676 -0
  102. data/hobo_files/plugin/lib/hobo/model_queries.rb +92 -0
  103. data/hobo_files/plugin/lib/hobo/model_support.rb +44 -0
  104. data/hobo_files/plugin/lib/hobo/password_string.rb +3 -0
  105. data/hobo_files/plugin/lib/hobo/predicate_dispatch.rb +78 -0
  106. data/hobo_files/plugin/lib/hobo/proc_binding.rb +32 -0
  107. data/hobo_files/plugin/lib/hobo/rapid.rb +447 -0
  108. data/hobo_files/plugin/lib/hobo/static_tags +92 -0
  109. data/hobo_files/plugin/lib/hobo/text.rb +3 -0
  110. data/hobo_files/plugin/lib/hobo/textile_string.rb +13 -0
  111. data/hobo_files/plugin/lib/hobo/undefined.rb +41 -0
  112. data/hobo_files/plugin/lib/hobo/undefined_access_error.rb +5 -0
  113. data/hobo_files/plugin/lib/hobo/where_fragment.rb +23 -0
  114. data/hobo_files/plugin/lib/rexml.rb +345 -0
  115. data/hobo_files/plugin/tags/core.dryml +6 -0
  116. data/hobo_files/plugin/tags/rapid.dryml +177 -0
  117. data/hobo_files/plugin/tags/rapid_editing.dryml +168 -0
  118. data/hobo_files/plugin/tags/rapid_navigation.dryml +95 -0
  119. data/hobo_files/plugin/tags/rapid_pages.dryml +175 -0
  120. data/hobo_files/plugin/tasks/environments.rake +19 -0
  121. data/hobo_files/plugin/tasks/hobo_tasks.rake +4 -0
  122. data/hobo_files/plugin/test/hobo_dryml_template_test.rb +7 -0
  123. data/hobo_files/plugin/test/hobo_test.rb +7 -0
  124. data/hobo_files/plugin/uninstall.rb +1 -0
  125. metadata +206 -0
@@ -0,0 +1,245 @@
1
+ module Kernel
2
+
3
+ def extract_options_from_args!(args) #:nodoc:
4
+ args.last.is_a?(Hash) ? args.pop : {}
5
+ end
6
+
7
+ end
8
+
9
+ class Object
10
+
11
+ def in?(array)
12
+ array.include?(self)
13
+ end
14
+
15
+ def not_in?(array)
16
+ not array.include?(self)
17
+ end
18
+
19
+ alias_method :is_a_without_multiple_args?, :is_a?
20
+ def is_a?(*args)
21
+ args.any? {|a| is_a_without_multiple_args?(a) }
22
+ end
23
+
24
+ # metaid
25
+ def metaclass; class << self; self; end; end
26
+ def meta_eval &blk; metaclass.instance_eval &blk; end
27
+
28
+ # Adds methods to a metaclass
29
+ def meta_def name, &blk
30
+ meta_eval { define_method name, &blk }
31
+ end
32
+
33
+ # Defines an instance method within a class
34
+ def class_def name, &blk
35
+ class_eval { define_method name, &blk }
36
+ end
37
+
38
+ end
39
+
40
+
41
+ class Module
42
+
43
+ def inheriting_attr_accessor(*names)
44
+ for name in names
45
+ class_eval %{
46
+ def #{name}
47
+ if defined? @#{name}
48
+ @#{name}
49
+ elsif superclass.respond_to?('#{name}')
50
+ superclass.#{name}
51
+ end
52
+ end
53
+ }
54
+ end
55
+ end
56
+
57
+ # Custom alias_method_chain that won't cause inifinite recursion if
58
+ # called twice.
59
+ # Calling alias_method_chain on alias_method_chain
60
+ # was just way to confusing, so I copied it :-/
61
+ def alias_method_chain(target, feature)
62
+ # Strip out punctuation on predicates or bang methods since
63
+ # e.g. target?_without_feature is not a valid method name.
64
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
65
+ yield(aliased_target, punctuation) if block_given?
66
+ without = "#{aliased_target}_without_#{feature}#{punctuation}"
67
+ unless without.in?(instance_methods)
68
+ alias_method without, target
69
+ alias_method target, "#{aliased_target}_with_#{feature}#{punctuation}"
70
+ end
71
+ end
72
+
73
+
74
+ # Fix delegate so it doesn't go bang if 'to' is nil
75
+ def delegate(*methods)
76
+ options = methods.pop
77
+ unless options.is_a?(Hash) && to = options[:to]
78
+ raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
79
+ end
80
+
81
+ methods.each do |method|
82
+ module_eval(<<-EOS, "(__DELEGATION__)", 1)
83
+ def #{method}(*args, &block)
84
+ (_to = #{to}) && _to.__send__(#{method.inspect}, *args, &block)
85
+ end
86
+ EOS
87
+ end
88
+ end
89
+
90
+
91
+ end
92
+
93
+ module Enumerable
94
+
95
+ def omap(method = nil, &b)
96
+ if method
97
+ map(&method)
98
+ else
99
+ map {|x| x.instance_eval(&b)}
100
+ end
101
+ end
102
+
103
+ def oselect(method = nil, &b)
104
+ if method
105
+ select(&method)
106
+ else
107
+ select {|x| x.instance_eval(&b)}
108
+ end
109
+ end
110
+
111
+ def ofind(method=nil, &b)
112
+ if method
113
+ find(&method)
114
+ else
115
+ find {|x| x.instance_eval(&b)}
116
+ end
117
+ end
118
+
119
+ def search(not_found=nil)
120
+ each do |x|
121
+ val = yield(x)
122
+ return val if val
123
+ end
124
+ not_found
125
+ end
126
+
127
+ def oany?(method=nil, &b)
128
+ if method
129
+ any?(&method)
130
+ else
131
+ any? {|x| x.instance_eval(&b)}
132
+ end
133
+ end
134
+
135
+ def oall?(method=nil, &b)
136
+ if method
137
+ all?(&method)
138
+ else
139
+ all? {|x| x.instance_eval(&b)}
140
+ end
141
+ end
142
+
143
+ def every(proc)
144
+ map(&proc)
145
+ end
146
+
147
+ def map_with_index
148
+ res = []
149
+ each_with_index {|x, i| res << yield(x, i)}
150
+ res
151
+ end
152
+
153
+ end
154
+
155
+ class Hash
156
+
157
+ def self.build(array)
158
+ array.inject({}) do |res, x|
159
+ k, v = yield x
160
+ res[k] = v
161
+ res
162
+ end
163
+ end
164
+
165
+ def select_hash(new_keys=nil)
166
+ res = {}
167
+ if block_given?
168
+ each {|k,v| res[k] = v if yield(k,v) }
169
+ else
170
+ new_keys.each {|k| res[k] = self[k] if self.has_key?(k)}
171
+ end
172
+ res
173
+ end
174
+
175
+ def map_hash
176
+ res = {}
177
+ each {|k,v| res[k] = yield(k,v) }
178
+ res
179
+ end
180
+
181
+ #alias_method :hobo_original_reject, :reject
182
+ def rejectX(keys=nil, &b)
183
+ if b
184
+ hobo_original_reject(&b)
185
+ else
186
+ res = {}.update(self) # can't use dup because it breaks with symbols
187
+ keys.each {|k| res.delete(k)}
188
+ res
189
+ end
190
+ end
191
+
192
+ def partition_hash(keys=nil)
193
+ yes = {}
194
+ no = {}
195
+ each do |k,v|
196
+ if block_given? ? yield(k,v) : keys.include?(k)
197
+ yes[k] = v
198
+ else
199
+ no[k] = v
200
+ end
201
+ end
202
+ [yes, no]
203
+ end
204
+
205
+ end
206
+
207
+ class <<ActiveRecord::Base
208
+ alias_method :[], :find
209
+ end
210
+
211
+
212
+ # --- Fix Chronic - can't parse '12th Jan' --- #
213
+ begin
214
+ require 'chronic'
215
+
216
+ module Chronic
217
+
218
+ class << self
219
+ def parse_with_hobo_fix(s)
220
+ parse_without_hobo_fix(if s =~ /^\s*\d+\s*(st|nd|rd|th)\s+[a-zA-Z]+(\s+\d+)?\s*$/
221
+ s.sub(/\s*\d+(st|nd|rd|th)/) {|s| s[0..-3]}
222
+ else
223
+ s
224
+ end)
225
+ end
226
+ alias_method_chain :parse, :hobo_fix
227
+ end
228
+ end
229
+ rescue MissingSourceFile; end
230
+
231
+
232
+
233
+ # --- Fix pp dumps - these break sometimes without this --- #
234
+ require 'pp'
235
+ module PP::ObjectMixin
236
+
237
+ alias_method :orig_pretty_print, :pretty_print
238
+ def pretty_print(q)
239
+ orig_pretty_print(q)
240
+ rescue
241
+ "[#PP-ERROR#]"
242
+ end
243
+
244
+ end
245
+
@@ -0,0 +1,130 @@
1
+
2
+ class Test::Unit::TestCase
3
+
4
+ def self.fixture_objects(model, *names)
5
+ fixtures model
6
+ names.each do |name|
7
+ class_eval "def #{name}; #{model}(:#{name}); end"
8
+ end
9
+ end
10
+
11
+ protected
12
+
13
+ module HoboTesting
14
+
15
+ class HoboHelpers
16
+ include Hobo::ControllerHelpers
17
+
18
+ def urlb
19
+ "http://example.com"
20
+ end
21
+ end
22
+
23
+ attr_reader :current_user
24
+
25
+ def is_redirected_to(x)
26
+ url = x.is_a?(String) ? x : object_url(x)
27
+ assert_response :redirect
28
+ assert_redirected_to(url)
29
+ follow_redirect!
30
+ assert_response :success
31
+ end
32
+
33
+ def logs_in_as(user, password)
34
+ @current_user = user
35
+ post login_url, :login => user.login, :password => password
36
+ assert_response :redirect, "#{user.login} failed to log in"
37
+ get homepage_url
38
+ assert_response :success
39
+ end
40
+
41
+ def visits(obj, action=nil, params={})
42
+ get object_url(obj, action, params)
43
+ assert_response :success
44
+ end
45
+
46
+ def cant_visit(obj, action=nil)
47
+ get object_url(obj, action)
48
+ assert_response :forbidden
49
+ end
50
+
51
+ def creates(klass, params)
52
+ replace_objects_in_params!(params)
53
+ post object_url(klass), klass.name.underscore => params
54
+ new_obj = assigns["this"]
55
+ flunk "validation errors: #{new_obj.errors.full_messages.join("\n")}" unless new_obj.errors.empty?
56
+ assert_response :redirect
57
+ new_obj
58
+ end
59
+
60
+ def deletes(object)
61
+ post object_url(object, "destroy")
62
+ assert_raise(ActiveRecord::RecordNotFound) { object.class.find(object.id) }
63
+ assert_response :redirect
64
+ end
65
+
66
+ def cant_create(klass, params)
67
+ replace_objects_in_params!(params)
68
+ post object_url(klass), klass.name.underscore => params
69
+ assert_response :forbidden
70
+ end
71
+
72
+ def can_see(s)
73
+ assert_select("body", Regexp.new(s.split.map { |w| Regexp.escape(w) }.join("\s*")),
74
+ current_should("be able to see: #{s}"))
75
+ end
76
+
77
+ alias_method :sees, :can_see
78
+
79
+ def cant_see(s)
80
+ assert_select("body", { :text => Regexp.new(s.split.map { |w| Regexp.escape(w) }.join("\s*")), :count => 0 },
81
+ current_should("not be able to see: #{s}"))
82
+ end
83
+
84
+ def creates_and_visits(klass, params)
85
+ new_obj = creates(klass, params)
86
+ visits(new_obj)
87
+ new_obj
88
+ end
89
+
90
+ def calls_method(object, method, params={})
91
+ post object_url(object, method), params
92
+ assert_response :success
93
+ end
94
+
95
+ def current_should(s)
96
+ "#{current_user.login} should #{s}"
97
+ end
98
+
99
+ def object_url(obj, action=nil, *param_hashes)
100
+ HoboHelpers.new.send(:object_url, obj, action, *param_hashes)
101
+ end
102
+
103
+ def replace_objects_in_params!(hash)
104
+ hash.each do |k,v|
105
+ if v.is_a? ActiveRecord::Base
106
+ hash[k] = "@" + Hobo.dom_id(v)
107
+ elsif v.is_a? Hash
108
+ replace_objects_in_params!(v)
109
+ end
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ def new_session
116
+ open_session do |sess|
117
+ sess.extend(HoboTesting)
118
+ session_opened(sess) if respond_to? :session_opened
119
+ yield sess if block_given?
120
+ end
121
+ end
122
+
123
+ def new_session_as(person, password)
124
+ new_session do |sess|
125
+ sess.logs_in_as(person, password)
126
+ yield sess if block_given?
127
+ end
128
+ end
129
+
130
+ end
@@ -0,0 +1,353 @@
1
+ class HoboError < RuntimeError; end
2
+
3
+ module Hobo
4
+
5
+ class RawJs < String; end
6
+
7
+ @models = []
8
+
9
+ class << self
10
+
11
+ attr_accessor :current_theme
12
+ attr_writer :developer_features
13
+
14
+ def developer_features?
15
+ @developer_features
16
+ end
17
+
18
+
19
+ def raw_js(s)
20
+ RawJs.new(s)
21
+ end
22
+
23
+
24
+ def user_model=(model)
25
+ @user_model = model && model.name
26
+ end
27
+
28
+
29
+ def user_model
30
+ @user_model && @user_model.constantize
31
+ end
32
+
33
+
34
+ def models=(models)
35
+ @models = models.every(:name)
36
+ end
37
+
38
+
39
+ def models
40
+ unless @models_loaded
41
+ Dir.entries("#{RAILS_ROOT}/app/models/").map do |f|
42
+ f =~ /.rb$/ and f.sub(/.rb$/, '').camelize.constantize
43
+ end
44
+ @models_loaded = true
45
+ end
46
+ @models.omap{constantize}
47
+ end
48
+
49
+
50
+ def register_model(model)
51
+ @models << model.name unless @models.include? model.name
52
+ end
53
+
54
+
55
+ def object_from_dom_id(dom_id)
56
+ return nil if dom_id == 'nil'
57
+
58
+ _, name, id, attr = *dom_id.match(/^([a-z_]+)_([0-9]+(?:_[0-9]+)*)(_[a-z_]+)?$/)
59
+ raise ArgumentError.new("invalid model-reference in dom id") unless name
60
+ if name
61
+ model_class = name.camelize.constantize rescue (raise ArgumentError.new("no such class in dom-id"))
62
+ return nil unless model_class
63
+ attr = attr[1..-1] if attr
64
+ obj = if false and attr and model_class.reflections[attr.to_sym].klass.superclass == ActiveRecord::Base
65
+ # DISABLED - Eager loading is broken - doesn't support ordering
66
+ # http://dev.rubyonrails.org/ticket/3438
67
+ # Don't do this for STI subclasses - it breaks!
68
+ model_class.find(id, :include => attr)
69
+ else
70
+ model_class.find(id)
71
+ end
72
+ attr ? obj.send(attr) : obj
73
+ end
74
+ end
75
+
76
+ def dom_id(obj, attr=nil)
77
+ if obj.nil?
78
+ raise HoboError, "Tried to get dom id of nil.#{attr}" if attr
79
+ return 'nil'
80
+ end
81
+
82
+ if obj.is_a?(Array) and obj.respond_to?(:proxy_owner)
83
+ attr = obj.proxy_reflection.name
84
+ obj = obj.proxy_owner
85
+ elsif !obj.respond_to?(:typed_id)
86
+ if attr
87
+ return dom_id(get_field(obj, attr))
88
+ else
89
+ raise ArgumentError, "Can't create dom id for #{obj.inspect}"
90
+ end
91
+ end
92
+ attr ? "#{obj.typed_id}_#{attr}" : obj.typed_id
93
+ end
94
+
95
+ def find_by_search(query)
96
+ sql = Hobo.models.map do |model|
97
+ if model.superclass == ActiveRecord::Base # filter out STI subclasses
98
+ cols = model.search_columns
99
+ if cols.blank?
100
+ nil
101
+ else
102
+ where = cols.map {|c| "(#{c} like ?)"}.join(' or ')
103
+ type = model.column_names.include?("type") ? "type" : "'#{model.name}'"
104
+ ActiveRecord::Base.send(:sanitize_sql,
105
+ ["select #{type} as type, id " +
106
+ "from #{model.table_name} " +
107
+ "where #{where}"] +
108
+ ["%#{query}%"] * cols.length)
109
+ end
110
+ end
111
+ end.compact.join(" union ")
112
+
113
+ rows = ActiveRecord::Base.connection.select_all(sql)
114
+ records = Hash.new {|h,k| h[k] = []}
115
+ for row in rows
116
+ records[row['type']] << row['id']
117
+ end
118
+ results = []
119
+ for type, ids in records
120
+ results.concat(type.constantize.find(:all, :conditions => "id in (#{ids * ','})"))
121
+ end
122
+
123
+ results
124
+ end
125
+
126
+ def add_routes(map)
127
+ require "#{RAILS_ROOT}/app/controllers/application" unless Object.const_defined? :ApplicationController
128
+
129
+ for model in Hobo.models
130
+ controller_name = "#{model.name.pluralize}Controller"
131
+ controller = controller_name.constantize if
132
+ File.exists?("#{RAILS_ROOT}/app/controllers/#{controller_name.underscore}.rb")
133
+ if controller
134
+ web_name = model.name.underscore.pluralize.downcase
135
+
136
+ # Simple support for composite models, we might later need a CompositeModelController
137
+ if model < Hobo::CompositeModel
138
+ map.connect "#{web_name}/:id", :controller => web_name, :action => 'show'
139
+
140
+ elsif controller < Hobo::ModelController
141
+
142
+ map.resources web_name, :collection => { :completions => :get }
143
+
144
+ for collection in controller.collections
145
+ new_method = Hobo.simple_has_many_association?(model.reflections[collection])
146
+ Hobo.add_collection_routes(map, web_name, collection, new_method)
147
+ end
148
+
149
+ for method in controller.web_methods
150
+ map.named_route("#{web_name.singularize}_#{method}",
151
+ "#{web_name}/:id/#{method}",
152
+ :controller => web_name,
153
+ :action => method.to_s,
154
+ :conditions => { :method => :post })
155
+ end
156
+
157
+ for view in controller.show_actions
158
+ map.named_route("#{web_name.singularize}_#{view}",
159
+ "#{web_name}/:id;#{view}",
160
+ :controller => web_name,
161
+ :action => view.to_s,
162
+ :conditions => { :method => :get })
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+
170
+ def add_collection_routes(map, controller_name, collection_name, new_method)
171
+ singular_name = collection_name.to_s.singularize
172
+ map.with_options :controller => controller_name, :conditions => { :method => :get } do |m|
173
+ m.named_route("#{controller_name.singularize}_#{collection_name}",
174
+ "#{controller_name}/:id/#{collection_name}",
175
+ :action => "show_#{collection_name}")
176
+
177
+ m.named_route("new_#{controller_name.singularize}_#{singular_name}",
178
+ "#{controller_name}/:id/#{collection_name}/new",
179
+ :action => "new_#{singular_name}") if new_method
180
+ end
181
+ end
182
+
183
+
184
+ def all_controllers
185
+ Hobo.models.map {|m| m.name.underscore.pluralize}
186
+ end
187
+
188
+
189
+ def simple_has_many_association?(array_or_reflection)
190
+ refl = array_or_reflection.is_a?(Array) ? array_or_reflection.proxy_reflection : array_or_reflection
191
+ return false unless refl.is_a?(ActiveRecord::Reflection::AssociationReflection)
192
+ refl.macro == :has_many and
193
+ (not refl.through_reflection) and
194
+ (not refl.options[:conditions])
195
+ end
196
+
197
+
198
+ def get_field(object, field)
199
+ if field.to_s =~ /\d+/
200
+ object[field.to_i]
201
+ else
202
+ object.send(field)
203
+ end
204
+ end
205
+
206
+
207
+ # --- Permissions --- #
208
+
209
+
210
+ def can_create?(person, object)
211
+ if object.is_a?(Class) and object < ActiveRecord::Base
212
+ object = object.new
213
+ object.created_by(person)
214
+ elsif Hobo.simple_has_many_association?(object)
215
+ object = object.new_without_appending
216
+ object.created_by(person)
217
+ end
218
+ check_permission(:create, person, object)
219
+ end
220
+
221
+
222
+ def can_update?(person, object, new)
223
+ check_permission(:update, person, object, new)
224
+ end
225
+
226
+
227
+ def can_edit?(person, object, field)
228
+ setter = "#{field}="
229
+ return false unless can_view?(person, object, field) and object.respond_to?(setter)
230
+
231
+ refl = object.class.reflections[field.to_sym] if object.is_a?(ActiveRecord::Base)
232
+
233
+ # has_many and polymorphic associations are not editable (for now)
234
+ return false if refl and (refl.macro == :has_many or refl.options[:polymorphic])
235
+
236
+ current = object.send(field)
237
+ new = object.duplicate
238
+ new.send(setter, if current == true
239
+ false
240
+ elsif current == false
241
+ true
242
+ elsif refl and refl.macro == :belongs_to
243
+ Hobo::Undefined.new(refl.klass)
244
+ else
245
+ Hobo::Undefined.new
246
+ end)
247
+
248
+ begin
249
+ if object.new_record?
250
+ check_permission(:create, person, new)
251
+ else
252
+ check_permission(:update, person, object, new)
253
+ end
254
+ rescue Hobo::UndefinedAccessError
255
+ false
256
+ end
257
+ end
258
+
259
+
260
+ def can_delete?(person, object)
261
+ check_permission(:delete, person, object)
262
+ end
263
+
264
+
265
+ # can_view? has special behaviour if it's passed a class or an
266
+ # association-proxy -- it instantiates the class, or creates a new
267
+ # instance "in" the association (new_without_appending), and tests
268
+ # the permission of this object. This means the permission methods
269
+ # in models can't rely on the instance being properly initialised.
270
+ # But it's important that it works like this because, in the case
271
+ # of an association proxy, we don't want to loose the information
272
+ # that the object belongs_to the proxy owner.
273
+ def can_view?(person, object, field=nil)
274
+ if field
275
+ field = field.to_sym if field.is_a? String
276
+ return false if object.is_a?(ActiveRecord::Base) and object.class.never_show?(field)
277
+ else
278
+ # Special support for classes (can view instances?)
279
+ if object.is_a?(Class) and object < ActiveRecord::Base
280
+ object = object.new
281
+ elsif object.is_a?(Array)
282
+ if object.respond_to?(:new_without_appending)
283
+ object = object.new_without_appending
284
+ elsif object.respond_to?(:member_class)
285
+ object = object.member_class.new
286
+ end
287
+ end
288
+ end
289
+ viewable = check_permission(:view, person, object, field)
290
+ if viewable and field and
291
+ ( (field_val = get_field(object, field)).is_a?(ActiveRecord::Base) or field_val.is_a?(Array) )
292
+ # also ask the current value if it is viewable
293
+ can_view?(person, field_val)
294
+ else
295
+ viewable
296
+ end
297
+ end
298
+
299
+
300
+ def can_call?(person, object, method)
301
+ return true if person.respond_to?(:super_user?) && person.super_user?
302
+
303
+ m = "#{method}_callable_by?"
304
+ object.respond_to?(m) && object.send(m, person)
305
+ end
306
+
307
+ # --- end permissions -- #
308
+
309
+
310
+ def static_tags
311
+ @static_tags ||= begin
312
+ path = File.join(File.dirname(__FILE__), "hobo/static_tags")
313
+ File.readlines(path).omap{chop}
314
+ end
315
+ end
316
+
317
+
318
+ private
319
+
320
+
321
+ def check_permission(permission, person, object, *args)
322
+ return true if person.respond_to?(:super_user?) and person.super_user?
323
+
324
+ return true if permission == :view && !(object.is_a?(ActiveRecord::Base) || object.is_a?(Hobo::CompositeModel))
325
+
326
+ obj_method = case permission
327
+ when :create; :creatable_by?
328
+ when :update; :updatable_by?
329
+ when :delete; :deletable_by?
330
+ when :view; :viewable_by?
331
+ end
332
+ p = if object.respond_to?(obj_method)
333
+ begin
334
+ object.send(obj_method, person, *args)
335
+ rescue Hobo::UndefinedAccessError
336
+ false
337
+ end
338
+ elsif object.class.respond_to?(obj_method)
339
+ object.class.send(obj_method, person, *args)
340
+ elsif !object.is_a?(Class) # No user fallback for class-level permissions
341
+ person_method = "can_#{permission}?".to_sym
342
+ if person.respond_to?(person_method)
343
+ person.send(person_method, object, *args)
344
+ else
345
+ # The object does not define permissions - you can only view it
346
+ permission == :view
347
+ end
348
+ end
349
+ end
350
+
351
+ end
352
+
353
+ end