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.
- data/LICENSE.txt +22 -0
- data/README.txt +18 -0
- data/bin/hobo +81 -0
- data/hobo_files/plugin/CHANGES.txt +963 -0
- data/hobo_files/plugin/LICENSE.txt +22 -0
- data/hobo_files/plugin/README +4 -0
- data/hobo_files/plugin/Rakefile +11 -0
- data/hobo_files/plugin/generators/hobo/hobo_generator.rb +37 -0
- data/hobo_files/plugin/generators/hobo/templates/application.dryml +2 -0
- data/hobo_files/plugin/generators/hobo/templates/guest.rb +31 -0
- data/hobo_files/plugin/generators/hobo_front_controller/USAGE +11 -0
- data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +90 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/controller.rb +51 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/functional_test.rb +18 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/helper.rb +2 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +43 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/login.dryml +44 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +18 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/signup.dryml +45 -0
- data/hobo_files/plugin/generators/hobo_model/USAGE +26 -0
- data/hobo_files/plugin/generators/hobo_model/hobo_model_generator.rb +38 -0
- data/hobo_files/plugin/generators/hobo_model/templates/fixtures.yml +11 -0
- data/hobo_files/plugin/generators/hobo_model/templates/migration.rb +13 -0
- data/hobo_files/plugin/generators/hobo_model/templates/model.rb +24 -0
- data/hobo_files/plugin/generators/hobo_model/templates/unit_test.rb +10 -0
- data/hobo_files/plugin/generators/hobo_model_controller/USAGE +30 -0
- data/hobo_files/plugin/generators/hobo_model_controller/hobo_model_controller_generator.rb +43 -0
- data/hobo_files/plugin/generators/hobo_model_controller/templates/controller.rb +5 -0
- data/hobo_files/plugin/generators/hobo_model_controller/templates/functional_test.rb +18 -0
- data/hobo_files/plugin/generators/hobo_model_controller/templates/helper.rb +2 -0
- data/hobo_files/plugin/generators/hobo_model_controller/templates/view.rhtml +2 -0
- data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +51 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +436 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/default_mapping.rb +11 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/banner.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_bodytop.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_01.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_02.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_03.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_04.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_bottom.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_left.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_right.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_top.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_blue.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_dblue.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_green.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_purple.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_red.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/logo.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/spinner.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_dblue.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_green.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_purple.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_red.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_01.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_02.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_03.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_04.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_bottom.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_left.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_right.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_top.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +390 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +104 -0
- data/hobo_files/plugin/generators/hobo_user_model/USAGE +26 -0
- data/hobo_files/plugin/generators/hobo_user_model/hobo_user_model_generator.rb +38 -0
- data/hobo_files/plugin/generators/hobo_user_model/templates/fixtures.yml +11 -0
- data/hobo_files/plugin/generators/hobo_user_model/templates/migration.rb +15 -0
- data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +58 -0
- data/hobo_files/plugin/generators/hobo_user_model/templates/unit_test.rb +10 -0
- data/hobo_files/plugin/init.rb +44 -0
- data/hobo_files/plugin/lib/action_view_extensions/base.rb +14 -0
- data/hobo_files/plugin/lib/active_record/has_many_association.rb +54 -0
- data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +22 -0
- data/hobo_files/plugin/lib/active_record/table_definition.rb +34 -0
- data/hobo_files/plugin/lib/extensions.rb +245 -0
- data/hobo_files/plugin/lib/extensions/test_case.rb +130 -0
- data/hobo_files/plugin/lib/hobo.rb +353 -0
- data/hobo_files/plugin/lib/hobo/HtmlString +3 -0
- data/hobo_files/plugin/lib/hobo/authenticated_user.rb +106 -0
- data/hobo_files/plugin/lib/hobo/authentication_support.rb +108 -0
- data/hobo_files/plugin/lib/hobo/composite_model.rb +66 -0
- data/hobo_files/plugin/lib/hobo/controller.rb +134 -0
- data/hobo_files/plugin/lib/hobo/controller_helpers.rb +135 -0
- data/hobo_files/plugin/lib/hobo/core.rb +475 -0
- data/hobo_files/plugin/lib/hobo/define_tags.rb +56 -0
- data/hobo_files/plugin/lib/hobo/dryml.rb +161 -0
- data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +126 -0
- data/hobo_files/plugin/lib/hobo/dryml/tag_module.rb +9 -0
- data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +57 -0
- data/hobo_files/plugin/lib/hobo/dryml/template.rb +586 -0
- data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +302 -0
- data/hobo_files/plugin/lib/hobo/dryml/template_handler.rb +19 -0
- data/hobo_files/plugin/lib/hobo/generator.rb +25 -0
- data/hobo_files/plugin/lib/hobo/html_string.rb +3 -0
- data/hobo_files/plugin/lib/hobo/lazy_hash.rb +28 -0
- data/hobo_files/plugin/lib/hobo/mapping_tags.rb +262 -0
- data/hobo_files/plugin/lib/hobo/markdown_string.rb +7 -0
- data/hobo_files/plugin/lib/hobo/model.rb +391 -0
- data/hobo_files/plugin/lib/hobo/model_controller.rb +676 -0
- data/hobo_files/plugin/lib/hobo/model_queries.rb +92 -0
- data/hobo_files/plugin/lib/hobo/model_support.rb +44 -0
- data/hobo_files/plugin/lib/hobo/password_string.rb +3 -0
- data/hobo_files/plugin/lib/hobo/predicate_dispatch.rb +78 -0
- data/hobo_files/plugin/lib/hobo/proc_binding.rb +32 -0
- data/hobo_files/plugin/lib/hobo/rapid.rb +447 -0
- data/hobo_files/plugin/lib/hobo/static_tags +92 -0
- data/hobo_files/plugin/lib/hobo/text.rb +3 -0
- data/hobo_files/plugin/lib/hobo/textile_string.rb +13 -0
- data/hobo_files/plugin/lib/hobo/undefined.rb +41 -0
- data/hobo_files/plugin/lib/hobo/undefined_access_error.rb +5 -0
- data/hobo_files/plugin/lib/hobo/where_fragment.rb +23 -0
- data/hobo_files/plugin/lib/rexml.rb +345 -0
- data/hobo_files/plugin/tags/core.dryml +6 -0
- data/hobo_files/plugin/tags/rapid.dryml +177 -0
- data/hobo_files/plugin/tags/rapid_editing.dryml +168 -0
- data/hobo_files/plugin/tags/rapid_navigation.dryml +95 -0
- data/hobo_files/plugin/tags/rapid_pages.dryml +175 -0
- data/hobo_files/plugin/tasks/environments.rake +19 -0
- data/hobo_files/plugin/tasks/hobo_tasks.rake +4 -0
- data/hobo_files/plugin/test/hobo_dryml_template_test.rb +7 -0
- data/hobo_files/plugin/test/hobo_test.rb +7 -0
- data/hobo_files/plugin/uninstall.rb +1 -0
- 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
|