hobo 0.7.2 → 0.7.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/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
|