hobo 0.5.3

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