hobo 0.7.2 → 0.7.3
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/hobo +24 -7
- data/hobo_files/plugin/CHANGES.txt +501 -0
- data/hobo_files/plugin/generators/hobo/hobo_generator.rb +8 -6
- data/hobo_files/plugin/generators/hobo/templates/application.dryml +3 -0
- data/hobo_files/plugin/generators/hobo/templates/dryml-support.js +132 -0
- data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +4 -5
- data/hobo_files/plugin/generators/hobo_model_resource/hobo_model_resource_generator.rb +75 -0
- data/hobo_files/plugin/generators/hobo_model_resource/templates/controller.rb +7 -0
- data/hobo_files/plugin/generators/hobo_model_resource/templates/functional_test.rb +8 -0
- data/hobo_files/plugin/generators/hobo_model_resource/templates/helper.rb +2 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo-rapid.js +30 -11
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/application.css +149 -92
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +0 -48
- data/hobo_files/plugin/init.rb +45 -13
- data/hobo_files/plugin/lib/action_view_extensions/base.rb +4 -3
- data/hobo_files/plugin/lib/active_record/association_proxy.rb +18 -0
- data/hobo_files/plugin/lib/active_record/association_reflection.rb +5 -0
- data/hobo_files/plugin/lib/active_record/has_many_association.rb +7 -11
- data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +8 -0
- data/hobo_files/plugin/lib/extensions/test_case.rb +1 -1
- data/hobo_files/plugin/lib/hobo.rb +38 -60
- data/hobo_files/plugin/lib/hobo/authentication_support.rb +1 -1
- data/hobo_files/plugin/lib/hobo/bundle.rb +131 -34
- data/hobo_files/plugin/lib/hobo/composite_model.rb +1 -1
- data/hobo_files/plugin/lib/hobo/controller.rb +7 -8
- data/hobo_files/plugin/lib/hobo/dev_controller.rb +21 -0
- data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +14 -8
- data/hobo_files/plugin/lib/hobo/dryml/dryml_support_controller.rb +13 -0
- data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +6 -7
- data/hobo_files/plugin/lib/hobo/dryml/template.rb +207 -73
- data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +67 -55
- data/hobo_files/plugin/lib/hobo/dryml/template_handler.rb +53 -3
- data/hobo_files/plugin/lib/hobo/hobo_helper.rb +75 -107
- data/hobo_files/plugin/lib/hobo/model.rb +236 -429
- data/hobo_files/plugin/lib/hobo/model_controller.rb +277 -437
- data/hobo_files/plugin/lib/hobo/model_router.rb +62 -29
- data/hobo_files/plugin/lib/hobo/rapid_helper.rb +48 -9
- data/hobo_files/plugin/lib/hobo/scopes.rb +98 -0
- data/hobo_files/plugin/lib/hobo/scopes/association_proxy_extensions.rb +31 -0
- data/hobo_files/plugin/lib/hobo/scopes/automatic_scopes.rb +282 -0
- data/hobo_files/plugin/lib/hobo/scopes/defined_scope_proxy_extender.rb +88 -0
- data/hobo_files/plugin/lib/hobo/scopes/scope_reflection.rb +18 -0
- data/hobo_files/plugin/lib/hobo/scopes/scoped_proxy.rb +59 -0
- data/hobo_files/plugin/lib/hobo/undefined.rb +2 -0
- data/hobo_files/plugin/lib/hobo/user.rb +31 -14
- data/hobo_files/plugin/lib/hobo/user_controller.rb +41 -27
- data/hobo_files/plugin/taglibs/core.dryml +9 -11
- data/hobo_files/plugin/taglibs/rapid.dryml +51 -108
- data/hobo_files/plugin/taglibs/rapid_editing.dryml +25 -25
- data/hobo_files/plugin/taglibs/rapid_forms.dryml +111 -79
- data/hobo_files/plugin/taglibs/rapid_generics.dryml +74 -0
- data/hobo_files/plugin/taglibs/rapid_navigation.dryml +23 -21
- data/hobo_files/plugin/taglibs/rapid_pages.dryml +83 -169
- data/hobo_files/plugin/taglibs/rapid_plus.dryml +16 -2
- data/hobo_files/plugin/taglibs/rapid_support.dryml +3 -3
- data/hobo_files/plugin/taglibs/rapid_user_pages.dryml +104 -0
- metadata +60 -55
- data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +0 -276
- data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +0 -9
- data/hobo_files/plugin/lib/active_record/table_definition.rb +0 -34
- data/hobo_files/plugin/lib/extensions.rb +0 -375
- data/hobo_files/plugin/lib/hobo/email_address.rb +0 -12
- data/hobo_files/plugin/lib/hobo/enum_string.rb +0 -50
- data/hobo_files/plugin/lib/hobo/field_declaration_dsl.rb +0 -43
- data/hobo_files/plugin/lib/hobo/field_spec.rb +0 -68
- data/hobo_files/plugin/lib/hobo/html_string.rb +0 -7
- data/hobo_files/plugin/lib/hobo/lazy_hash.rb +0 -40
- data/hobo_files/plugin/lib/hobo/markdown_string.rb +0 -11
- data/hobo_files/plugin/lib/hobo/migrations.rb +0 -12
- data/hobo_files/plugin/lib/hobo/model_queries.rb +0 -117
- data/hobo_files/plugin/lib/hobo/password_string.rb +0 -7
- data/hobo_files/plugin/lib/hobo/percentage.rb +0 -14
- data/hobo_files/plugin/lib/hobo/predicate_dispatch.rb +0 -78
- data/hobo_files/plugin/lib/hobo/proc_binding.rb +0 -32
- data/hobo_files/plugin/lib/hobo/text.rb +0 -3
- data/hobo_files/plugin/lib/hobo/textile_string.rb +0 -25
- data/hobo_files/plugin/lib/hobo/where_fragment.rb +0 -28
@@ -2,9 +2,19 @@ class ActionController::Routing::RouteSet
|
|
2
2
|
# Monkey patch this method so routes are reloaded on *every*
|
3
3
|
# request. Without this Rails checks the mtime of config/routes.rb
|
4
4
|
# which doesn't take into account Hobo's auto routing
|
5
|
-
def reload
|
6
|
-
|
5
|
+
#def reload
|
6
|
+
# # TODO: This can get slow - quicker to stat routes.rb and the
|
7
|
+
# # controllers and only do a load if there's been a change
|
8
|
+
# load!
|
9
|
+
#end
|
10
|
+
|
11
|
+
# temporay hack -- reload assemble.rb whenever routes need reloading
|
12
|
+
def reload_with_hobo_assemble
|
13
|
+
load "#{RAILS_ROOT}/app/assemble.rb" if File.exists? "#{RAILS_ROOT}/app/assemble.rb"
|
14
|
+
reload_without_hobo_assemble
|
7
15
|
end
|
16
|
+
alias_method_chain :reload, :hobo_assemble
|
17
|
+
|
8
18
|
end
|
9
19
|
|
10
20
|
module Hobo
|
@@ -16,19 +26,27 @@ module Hobo
|
|
16
26
|
class << self
|
17
27
|
|
18
28
|
def reset_linkables
|
19
|
-
@linkable =
|
29
|
+
@linkable =Set.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def linkable_key(klass, action, options)
|
33
|
+
opts = options.map { |k, v| "#{k}=#{v}" unless v.blank? }.compact.join(',')
|
34
|
+
"#{klass.name}/#{action}/#{opts}"
|
20
35
|
end
|
21
36
|
|
22
|
-
def linkable(
|
23
|
-
|
37
|
+
def linkable!(klass, action, options={})
|
38
|
+
options[:method] ||= :get
|
39
|
+
@linkable << linkable_key(klass, action, options)
|
24
40
|
end
|
25
41
|
|
26
|
-
def linkable?(
|
27
|
-
|
42
|
+
def linkable?(klass, action, options={})
|
43
|
+
options[:method] ||= :get
|
44
|
+
@linkable.member? linkable_key(klass, action, options)
|
28
45
|
end
|
29
46
|
|
30
47
|
def add_routes(map)
|
31
48
|
reset_linkables
|
49
|
+
|
32
50
|
begin
|
33
51
|
ActiveRecord::Base.connection.reconnect! unless ActiveRecord::Base.connection.active?
|
34
52
|
rescue
|
@@ -45,11 +63,15 @@ module Hobo
|
|
45
63
|
# Any directory inside app/controllers defines a subsite
|
46
64
|
subsites = Dir["#{APP_ROOT}/controllers/*"].map { |f| File.basename(f) if File.directory?(f) }.compact
|
47
65
|
subsites.each { |subsite| add_routes_for(map, subsite) }
|
66
|
+
|
67
|
+
add_developer_routes(map) if Hobo.developer_features?
|
48
68
|
end
|
49
69
|
|
50
70
|
|
51
71
|
def add_routes_for(map, subsite)
|
52
72
|
module_name = subsite._?.camelize
|
73
|
+
|
74
|
+
# FIXME: This should go directly to the controllers, not load the models first.
|
53
75
|
Hobo.models.each do |model|
|
54
76
|
controller_name = "#{model.name.pluralize}Controller"
|
55
77
|
is_defined = if subsite
|
@@ -59,12 +81,18 @@ module Hobo
|
|
59
81
|
end
|
60
82
|
controller_filename = File.join(*["#{APP_ROOT}/controllers", subsite, "#{controller_name.underscore}.rb"].compact)
|
61
83
|
if is_defined || File.exists?(controller_filename)
|
62
|
-
|
63
|
-
controller = owner_module.const_get(controller_name)
|
84
|
+
controller = (subsite ? "#{module_name}::#{controller_name}" : controller_name).constantize
|
64
85
|
ModelRouter.new(map, model, controller, subsite)
|
65
86
|
end
|
66
87
|
end
|
67
88
|
end
|
89
|
+
|
90
|
+
|
91
|
+
def add_developer_routes(map)
|
92
|
+
map.dryml_support "dryml/:action", :controller => "hobo/dryml/dryml_support"
|
93
|
+
map.dev_support "dev/:action", :controller => "hobo/dev"
|
94
|
+
end
|
95
|
+
|
68
96
|
end
|
69
97
|
|
70
98
|
|
@@ -111,6 +139,9 @@ module Hobo
|
|
111
139
|
def resource_routes
|
112
140
|
# We re-implement resource routing - routes are not created for
|
113
141
|
# actions that the controller does not provide
|
142
|
+
|
143
|
+
# FIX ME -- what about routes with formats (e.g. .xml)?
|
144
|
+
|
114
145
|
linkable_route(plural, plural, :index, :conditions => { :method => :get })
|
115
146
|
|
116
147
|
linkable_route("new_#{singular}", "#{plural}/new", :new, :conditions => { :method => :get })
|
@@ -126,49 +157,47 @@ module Hobo
|
|
126
157
|
|
127
158
|
def collection_routes
|
128
159
|
controller.collections.each do |collection|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
:conditions => { :method => :get })
|
160
|
+
linkable_route("#{singular}_#{collection}",
|
161
|
+
"#{plural}/:id/#{collection}",
|
162
|
+
collection.to_s,
|
163
|
+
:conditions => { :method => :get })
|
134
164
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
165
|
+
if Hobo.simple_has_many_association?(model.reflections[collection])
|
166
|
+
linkable_route("new_#{singular}_#{collection.to_s.singularize}",
|
167
|
+
"#{plural}/:id/#{collection}/new",
|
168
|
+
"new_#{collection.to_s.singularize}",
|
169
|
+
:conditions => { :method => :get })
|
170
|
+
end
|
139
171
|
end
|
140
172
|
end
|
141
173
|
|
142
174
|
|
143
175
|
def web_method_routes
|
144
176
|
controller.web_methods.each do |method|
|
145
|
-
|
146
|
-
:action => method.to_s, :conditions => { :method => :post })
|
177
|
+
linkable_route("#{plural.singularize}_#{method}", "#{plural}/:id/#{method}", method.to_s, :conditions => { :method => :post })
|
147
178
|
end
|
148
179
|
end
|
149
180
|
|
150
181
|
|
151
182
|
def index_action_routes
|
152
183
|
controller.index_actions.each do |view|
|
153
|
-
|
154
|
-
:action => view.to_s, :conditions => { :method => :get })
|
184
|
+
linkable_route("#{view}_#{plural}", "#{plural}/#{view}", view.to_s, :conditions => { :method => :get })
|
155
185
|
end
|
156
186
|
end
|
157
187
|
|
158
188
|
|
159
189
|
def show_action_routes
|
160
190
|
controller.show_actions.each do |view|
|
161
|
-
|
162
|
-
:action => view.to_s, :conditions => { :method => :get })
|
191
|
+
linkable_route("#{plural.singularize}_#{view}", "#{plural}/:id/#{view}", view.to_s, :conditions => { :method => :get })
|
163
192
|
end
|
164
193
|
end
|
165
194
|
|
166
195
|
|
167
196
|
def user_routes
|
168
197
|
prefix = plural == "users" ? "" : "#{singular}_"
|
169
|
-
|
170
|
-
|
171
|
-
|
198
|
+
linkable_route("#{singular}_login", "#{prefix}login", 'login')
|
199
|
+
linkable_route("#{singular}_logout", "#{prefix}logout", 'logout')
|
200
|
+
linkable_route("#{singular}_signup", "#{prefix}signup", 'signup')
|
172
201
|
end
|
173
202
|
|
174
203
|
|
@@ -187,8 +216,12 @@ module Hobo
|
|
187
216
|
end
|
188
217
|
|
189
218
|
|
190
|
-
def linkable_route(name, route, action, options)
|
191
|
-
named_route(name, route, options.merge(:action => action.to_s)) and
|
219
|
+
def linkable_route(name, route, action, options={})
|
220
|
+
named_route(name, route, options.merge(:action => action.to_s)) and
|
221
|
+
begin
|
222
|
+
linkable_options = { :method => options[:conditions]._?[:method], :subsite => subsite }
|
223
|
+
self.class.linkable!(model, action, linkable_options)
|
224
|
+
end
|
192
225
|
end
|
193
226
|
|
194
227
|
|
@@ -1,8 +1,20 @@
|
|
1
1
|
module Hobo::RapidHelper
|
2
2
|
|
3
|
-
def
|
4
|
-
|
3
|
+
def rapid_build_callbacks(options)
|
4
|
+
callbacks = {}
|
5
|
+
options.each do |callback, code|
|
6
|
+
if AJAX_CALLBACKS.include?(callback.to_sym)
|
7
|
+
name = 'on' + callback.to_s.capitalize
|
8
|
+
callbacks[name] = "function(request){#{code}}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
callbacks
|
12
|
+
end
|
13
|
+
|
5
14
|
|
15
|
+
def options_for_hobo_ajax(options)
|
16
|
+
js_options = rapid_build_callbacks(options)
|
17
|
+
|
6
18
|
js_options['asynchronous'] = false if options[:type] == :synchronous
|
7
19
|
js_options['method'] = method_option_to_s(options[:method]) if options[:method]
|
8
20
|
js_options['evalScripts'] = false if options[:script] == false
|
@@ -11,7 +23,8 @@ module Hobo::RapidHelper
|
|
11
23
|
js_options['resultUpdate'] = js_result_updates(options[:result_update]) if options[:result_update]
|
12
24
|
js_options['resetForm'] = options[:reset_form] if options.has_key?(:reset_form)
|
13
25
|
js_options['refocusForm'] = options[:refocus_form] if options.has_key?(:refocus_form)
|
14
|
-
js_options['spinnerNextTo'] = options[:spinner_next_to] if options.has_key?(:spinner_next_to)
|
26
|
+
js_options['spinnerNextTo'] = js_str(options[:spinner_next_to]) if options.has_key?(:spinner_next_to)
|
27
|
+
js_options['message'] = js_str(options[:message]) if options[:message]
|
15
28
|
|
16
29
|
js_options.empty? ? nil : options_for_javascript(js_options)
|
17
30
|
end
|
@@ -39,7 +52,7 @@ module Hobo::RapidHelper
|
|
39
52
|
end
|
40
53
|
|
41
54
|
|
42
|
-
def ajax_updater(url_or_form,
|
55
|
+
def ajax_updater(url_or_form, update, options={})
|
43
56
|
options ||= {}
|
44
57
|
options.symbolize_keys!
|
45
58
|
|
@@ -49,7 +62,7 @@ module Hobo::RapidHelper
|
|
49
62
|
js_str(url_or_form)
|
50
63
|
end
|
51
64
|
js_options = options_for_hobo_ajax(options)
|
52
|
-
args = [target,
|
65
|
+
args = [target, js_updates(update), js_options].compact
|
53
66
|
|
54
67
|
confirm = options.delete(:confirm)
|
55
68
|
|
@@ -83,7 +96,7 @@ module Hobo::RapidHelper
|
|
83
96
|
blank_message = attributes.delete(:blank_message) || "(click to edit)"
|
84
97
|
|
85
98
|
attributes = add_classes(attributes, behaviour_class)
|
86
|
-
attributes.update(:hobo_model_id =>
|
99
|
+
attributes.update(:hobo_model_id => dom_id,
|
87
100
|
:hobo_blank_message => blank_message,
|
88
101
|
:if_blank => blank_message,
|
89
102
|
:no_wrapper => false)
|
@@ -96,12 +109,38 @@ module Hobo::RapidHelper
|
|
96
109
|
|
97
110
|
|
98
111
|
|
99
|
-
|
100
|
-
|
101
|
-
|
112
|
+
AJAX_CALLBACKS = [ :before, :success, :failure, :complete ]
|
113
|
+
|
114
|
+
AJAX_ATTRS = AJAX_CALLBACKS + [ :type, :method,
|
115
|
+
:script, :form, :params, :confirm, :message,
|
116
|
+
:reset_form, :refocus_form, :result_update, :spinner_next_to ]
|
102
117
|
|
103
118
|
|
104
119
|
def editor_class
|
105
120
|
end
|
106
121
|
|
122
|
+
|
123
|
+
def through_collection_names(object=this)
|
124
|
+
object.class.reflections.values.select do |refl|
|
125
|
+
refl.macro == :has_many && refl.options[:through]
|
126
|
+
end.map {|x| x.options[:through]}
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def primary_collection_name(object=this)
|
131
|
+
dependent_collection_names = object.class.reflections.values.select do |refl|
|
132
|
+
refl.macro == :has_many && refl.options[:dependent]
|
133
|
+
end.*.name
|
134
|
+
|
135
|
+
(dependent_collection_names - through_collection_names(object)).first
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
def non_through_collections(object=this)
|
140
|
+
names = object.class.reflections.values.select do |refl|
|
141
|
+
refl.macro == :has_many
|
142
|
+
end.*.name
|
143
|
+
|
144
|
+
names - through_collection_names
|
145
|
+
end
|
107
146
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Hobo
|
2
|
+
|
3
|
+
module Scopes
|
4
|
+
|
5
|
+
def self.included_in_class(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
|
8
|
+
class << base
|
9
|
+
alias_method_chain :has_many, :defined_scopes
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
include AutomaticScopes
|
16
|
+
|
17
|
+
def defined_scopes
|
18
|
+
@defined_scopes
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def def_scope(name, scope=nil, &block)
|
23
|
+
@defined_scopes ||= {}
|
24
|
+
@defined_scopes[name.to_sym] = block || scope
|
25
|
+
|
26
|
+
meta_def(name) do |*args|
|
27
|
+
ScopedProxy.new(self, block ? block.call(*args) : scope)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def alias_scope(new_name, old_name)
|
33
|
+
metaclass.send(:alias_method, new_name, old_name)
|
34
|
+
defined_scopes[new_name] = defined_scopes[old_name]
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def has_many_with_defined_scopes(name, options={}, &block)
|
39
|
+
if options.has_key?(:extend) || block
|
40
|
+
# Normal has_many
|
41
|
+
has_many_without_defined_scopes(name, options, &block)
|
42
|
+
else
|
43
|
+
options[:extend] = Hobo::Scopes::DefinedScopeProxyExtender
|
44
|
+
has_many_without_defined_scopes(name, options, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# --- monkey-patches to allow :scope key on has_many, has_one and belongs_to ---
|
50
|
+
|
51
|
+
def create_has_many_reflection(association_id, options, &extension)
|
52
|
+
options.assert_valid_keys(
|
53
|
+
:class_name, :table_name, :foreign_key,
|
54
|
+
:dependent,
|
55
|
+
:select, :conditions, :include, :order, :group, :limit, :offset,
|
56
|
+
:as, :through, :source, :source_type,
|
57
|
+
:uniq,
|
58
|
+
:finder_sql, :counter_sql,
|
59
|
+
:before_add, :after_add, :before_remove, :after_remove,
|
60
|
+
:extend,
|
61
|
+
:scope
|
62
|
+
)
|
63
|
+
|
64
|
+
options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given?
|
65
|
+
|
66
|
+
create_reflection(:has_many, association_id, options, self)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_has_one_reflection(association_id, options)
|
70
|
+
options.assert_valid_keys(
|
71
|
+
:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :scope
|
72
|
+
)
|
73
|
+
|
74
|
+
create_reflection(:has_one, association_id, options, self)
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_belongs_to_reflection(association_id, options)
|
78
|
+
options.assert_valid_keys(
|
79
|
+
:class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
|
80
|
+
:counter_cache, :extend, :polymorphic, :scope
|
81
|
+
)
|
82
|
+
|
83
|
+
reflection = create_reflection(:belongs_to, association_id, options, self)
|
84
|
+
|
85
|
+
if options[:polymorphic]
|
86
|
+
reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
|
87
|
+
end
|
88
|
+
|
89
|
+
reflection
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
ActiveRecord::Associations::AssociationProxy.send(:include, Hobo::Scopes::AssociationProxyExtensions)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Add support for :scope => :my_scope to has_many and :belongs_to
|
2
|
+
|
3
|
+
module Hobo
|
4
|
+
|
5
|
+
module Scopes
|
6
|
+
|
7
|
+
module AssociationProxyExtensions
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
alias_method_chain :conditions, :hobo_scopes
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def conditions_with_hobo_scopes
|
16
|
+
scope_conditions = if (scope_name = @reflection.options[:scope])
|
17
|
+
target_class = @reflection.klass
|
18
|
+
target_class.send(scope_name).scope(:find)[:conditions]
|
19
|
+
end
|
20
|
+
if scope_conditions && conditions_without_hobo_scopes
|
21
|
+
"#{conditions_without_hobo_scopes} AND #{scope_conditions}"
|
22
|
+
else
|
23
|
+
scope_conditions || conditions_without_hobo_scopes
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
module Hobo
|
2
|
+
|
3
|
+
module Scopes
|
4
|
+
|
5
|
+
module AutomaticScopes
|
6
|
+
|
7
|
+
def create_automatic_scope(name)
|
8
|
+
ScopeBuilder.new(self, name).create_scope
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
# The methods on this module add scopes to the given class
|
14
|
+
class ScopeBuilder
|
15
|
+
|
16
|
+
def initialize(klass, name)
|
17
|
+
@klass = klass
|
18
|
+
@name = name.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :name
|
22
|
+
|
23
|
+
def create_scope
|
24
|
+
matched_scope = true
|
25
|
+
|
26
|
+
|
27
|
+
# --- Association Queries --- #
|
28
|
+
|
29
|
+
# with_players(player1, player2)
|
30
|
+
if name =~ /^with_(.*)/ && (refl = reflection($1))
|
31
|
+
|
32
|
+
def_scope do |*records|
|
33
|
+
records = records.flatten.compact.map {|r| find_if_named(refl, r) }
|
34
|
+
exists_sql = ([exists_sql_condition(refl)] * records.length).join(" AND ")
|
35
|
+
{ :conditions => [exists_sql] + records }
|
36
|
+
end
|
37
|
+
|
38
|
+
# with_player(a_player)
|
39
|
+
elsif name =~ /^with_(.*)/ && (refl = reflection($1.pluralize))
|
40
|
+
|
41
|
+
exists_sql = exists_sql_condition(refl)
|
42
|
+
def_scope do |record|
|
43
|
+
record = find_if_named(refl, record)
|
44
|
+
{ :conditions => [exists_sql, record] }
|
45
|
+
end
|
46
|
+
|
47
|
+
# without_players(player1, player2)
|
48
|
+
elsif name =~ /^without_(.*)/ && (refl = reflection($1))
|
49
|
+
|
50
|
+
def_scope do |*records|
|
51
|
+
records = records.flatten.compact.map {|r| find_if_named(refl, r) }
|
52
|
+
exists_sql = ([exists_sql_condition(refl)] * records.length).join(" AND ")
|
53
|
+
{ :conditions => ["NOT (#{exists_sql})"] + records }
|
54
|
+
end
|
55
|
+
|
56
|
+
# without_player(a_player)
|
57
|
+
elsif name =~ /^without_(.*)/ && (refl = reflection($1.pluralize))
|
58
|
+
|
59
|
+
exists_sql = exists_sql_condition(refl)
|
60
|
+
def_scope do |record|
|
61
|
+
record = find_if_named(refl, record)
|
62
|
+
{ :conditions => ["NOT #{exists_sql}", record] }
|
63
|
+
end
|
64
|
+
|
65
|
+
# team_is(a_team)
|
66
|
+
elsif name =~ /^(.*)_is$/ && (refl = reflection($1)) && refl.macro.in?([:has_one, :belongs_to])
|
67
|
+
|
68
|
+
if refl.options[:polymorphic]
|
69
|
+
def_scope do |record|
|
70
|
+
record = find_if_named(refl, record)
|
71
|
+
{ :conditions => ["#{foreign_key_column refl} = ? AND #{$1}_type = ?", record, record.class.name] }
|
72
|
+
end
|
73
|
+
else
|
74
|
+
def_scope do |record|
|
75
|
+
record = find_if_named(refl, record)
|
76
|
+
{ :conditions => ["#{foreign_key_column refl} = ?", record] }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# team_is(a_team)
|
81
|
+
elsif name =~ /^(.*)_is_not$/ && (refl = reflection($1)) && refl.macro.in?([:has_one, :belongs_to])
|
82
|
+
|
83
|
+
if refl.options[:polymorphic]
|
84
|
+
def_scope do |record|
|
85
|
+
record = find_if_named(refl, record)
|
86
|
+
{ :conditions => ["#{foreign_key_column refl} <> ? OR #{name}_type <> ?", record, record.class.name] }
|
87
|
+
end
|
88
|
+
else
|
89
|
+
def_scope do |record|
|
90
|
+
record = find_if_named(refl, record)
|
91
|
+
{ :conditions => ["#{foreign_key_column refl} <> ?", record] }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# --- Column Queries --- #
|
97
|
+
|
98
|
+
# name_is(str)
|
99
|
+
elsif name =~ /^(.*)_is$/ && (col = column($1))
|
100
|
+
|
101
|
+
def_scope do |str|
|
102
|
+
{ :conditions => ["#{column_sql(col)} = ?", str] }
|
103
|
+
end
|
104
|
+
|
105
|
+
# name_is_not(str)
|
106
|
+
elsif name =~ /^(.*)_is_not$/ && (col = column($1))
|
107
|
+
|
108
|
+
def_scope do |str|
|
109
|
+
{ :conditions => ["#{column_sql(col)} <> ?", str] }
|
110
|
+
end
|
111
|
+
|
112
|
+
# name_contains(str)
|
113
|
+
elsif name =~ /^(.*)_contains$/ && (col = column($1))
|
114
|
+
|
115
|
+
def_scope do |str|
|
116
|
+
{ :conditions => ["#{column_sql(col)} LIKE ?", "%#{str}%"] }
|
117
|
+
end
|
118
|
+
|
119
|
+
# name_does_not_contain
|
120
|
+
elsif name =~ /^(.*)_does_not_contain$/ && (col = column($1))
|
121
|
+
|
122
|
+
def_scope do |str|
|
123
|
+
{ :conditions => ["#{column_sql(col)} NOT LIKE ?", "%#{str}%"] }
|
124
|
+
end
|
125
|
+
|
126
|
+
# name_starts(str)
|
127
|
+
elsif name =~ /^(.*)_contains$/ && (col = column($1))
|
128
|
+
|
129
|
+
def_scope do |str|
|
130
|
+
{ :conditions => ["#{column_sql(col)} LIKE ?", "#{str}%"] }
|
131
|
+
end
|
132
|
+
|
133
|
+
# name_does_not_start
|
134
|
+
elsif name =~ /^(.*)_does_not_contain$/ && (col = column($1))
|
135
|
+
|
136
|
+
def_scope do |str|
|
137
|
+
{ :conditions => ["#{column_sql(col)} NOT LIKE ?", "#{str}%"] }
|
138
|
+
end
|
139
|
+
|
140
|
+
# name_ends(str)
|
141
|
+
elsif name =~ /^(.*)_contains$/ && (col = column($1))
|
142
|
+
|
143
|
+
def_scope do |str|
|
144
|
+
{ :conditions => ["#{column_sql(col)} LIKE ?", "%#{str}"] }
|
145
|
+
end
|
146
|
+
|
147
|
+
# name_does_not_end(str)
|
148
|
+
elsif name =~ /^(.*)_does_not_contain$/ && (col = column($1))
|
149
|
+
|
150
|
+
def_scope do |str|
|
151
|
+
{ :conditions => ["#{column_sql(col)} NOT LIKE ?", "%#{str}"] }
|
152
|
+
end
|
153
|
+
|
154
|
+
# published
|
155
|
+
elsif (col = column($1)) && (col.type == :boolean)
|
156
|
+
|
157
|
+
def_scope do
|
158
|
+
{ :conditions => "#{column_sql(col)} = 1" }
|
159
|
+
end
|
160
|
+
|
161
|
+
# not_published
|
162
|
+
elsif (col = column($1)) && (col.type == :boolean)
|
163
|
+
|
164
|
+
def_scope do
|
165
|
+
{ :conditions => "#{column_sql(col)} <> 1" }
|
166
|
+
end
|
167
|
+
|
168
|
+
# published_before(time)
|
169
|
+
elsif name =~ /^(.*)_before$/ && (col = column("#{$1}_at")) && col.type.in?([:date, :datetime, :time, :timestamp])
|
170
|
+
|
171
|
+
def_scope do |time|
|
172
|
+
{ :conditions => ["#{column_sql(col)} < ?", time] }
|
173
|
+
end
|
174
|
+
|
175
|
+
# published_after(time)
|
176
|
+
elsif name =~ /^(.*)_after$/ && (col = column("#{$1}_at")) && col.type.in?([:date, :datetime, :time, :timestamp])
|
177
|
+
|
178
|
+
def_scope do |time|
|
179
|
+
{ :conditions => ["#{column_sql(col)} > ?", time] }
|
180
|
+
end
|
181
|
+
|
182
|
+
# published_between(time1, time2)
|
183
|
+
elsif name =~ /^(.*)_between$/ && (col = column("#{$1}_at")) && col.type.in?([:date, :datetime, :time, :timestamp])
|
184
|
+
|
185
|
+
def_scope do |time1, time2|
|
186
|
+
{ :conditions => ["#{column_sql(col)} >= ? AND #{column_sql(col)} =< ?", time1, time2] }
|
187
|
+
end
|
188
|
+
|
189
|
+
else
|
190
|
+
|
191
|
+
case name
|
192
|
+
|
193
|
+
when "recent"
|
194
|
+
def_scope do |*args|
|
195
|
+
count = args.first || 3
|
196
|
+
{ :limit => count, :order => "#{@klass.table_name}.created_at DESC" }
|
197
|
+
end
|
198
|
+
|
199
|
+
when "limit"
|
200
|
+
def_scope do |count|
|
201
|
+
{ :limit => count }
|
202
|
+
end
|
203
|
+
|
204
|
+
when "order_by"
|
205
|
+
def_scope do |*args|
|
206
|
+
field, asc = args
|
207
|
+
{ :order => "#{field} #{asc._?.upcase}" }
|
208
|
+
end
|
209
|
+
|
210
|
+
else
|
211
|
+
matched_scope = false
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
matched_scope
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
def column_sql(column)
|
220
|
+
"#{@klass.table_name}.#{column.name}"
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
def exists_sql_condition(reflection)
|
225
|
+
owner = @klass
|
226
|
+
owner_primary_key = "#{owner.table_name}.#{owner.primary_key}"
|
227
|
+
if reflection.options[:through]
|
228
|
+
join_table = reflection.through_reflection.klass.table_name
|
229
|
+
source_fkey = reflection.source_reflection.primary_key_name
|
230
|
+
owner_fkey = reflection.through_reflection.primary_key_name
|
231
|
+
"EXISTS (SELECT * FROM #{join_table} " +
|
232
|
+
"WHERE #{join_table}.#{source_fkey} = ? AND #{join_table}.#{owner_fkey} = #{owner_primary_key})"
|
233
|
+
else
|
234
|
+
related = reflection.klass
|
235
|
+
foreign_key = reflection.primary_key_name
|
236
|
+
|
237
|
+
"EXISTS (SELECT * FROM #{related.table_name} " +
|
238
|
+
"WHERE #{related.table_name}.#{foreign_key} = #{owner_primary_key} AND " +
|
239
|
+
"#{related.table_name}.#{related.primary_key} = ?"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
def find_if_named(reflection, string_or_record)
|
245
|
+
if string_or_record.is_a?(String)
|
246
|
+
name = string_or_record
|
247
|
+
reflection.klass.named(name)
|
248
|
+
else
|
249
|
+
string_or_record
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
def column(name)
|
255
|
+
@klass.column(name)
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
def reflection(name)
|
260
|
+
@klass.reflections[name.to_sym]
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
def def_scope(options={}, &block)
|
265
|
+
@klass.send(:def_scope, name, options, &block)
|
266
|
+
end
|
267
|
+
|
268
|
+
|
269
|
+
def primary_key_column(refl)
|
270
|
+
"#{refl.klass.table_name}.#{refl.klass.primary_key}"
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
def foreign_key_column(refl)
|
275
|
+
"#{@klass.table_name}.#{refl.primary_key_name}"
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|