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.
Files changed (77) hide show
  1. data/bin/hobo +24 -7
  2. data/hobo_files/plugin/CHANGES.txt +501 -0
  3. data/hobo_files/plugin/generators/hobo/hobo_generator.rb +8 -6
  4. data/hobo_files/plugin/generators/hobo/templates/application.dryml +3 -0
  5. data/hobo_files/plugin/generators/hobo/templates/dryml-support.js +132 -0
  6. data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +4 -5
  7. data/hobo_files/plugin/generators/hobo_model_resource/hobo_model_resource_generator.rb +75 -0
  8. data/hobo_files/plugin/generators/hobo_model_resource/templates/controller.rb +7 -0
  9. data/hobo_files/plugin/generators/hobo_model_resource/templates/functional_test.rb +8 -0
  10. data/hobo_files/plugin/generators/hobo_model_resource/templates/helper.rb +2 -0
  11. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo-rapid.js +30 -11
  12. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/application.css +149 -92
  13. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +0 -48
  14. data/hobo_files/plugin/init.rb +45 -13
  15. data/hobo_files/plugin/lib/action_view_extensions/base.rb +4 -3
  16. data/hobo_files/plugin/lib/active_record/association_proxy.rb +18 -0
  17. data/hobo_files/plugin/lib/active_record/association_reflection.rb +5 -0
  18. data/hobo_files/plugin/lib/active_record/has_many_association.rb +7 -11
  19. data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +8 -0
  20. data/hobo_files/plugin/lib/extensions/test_case.rb +1 -1
  21. data/hobo_files/plugin/lib/hobo.rb +38 -60
  22. data/hobo_files/plugin/lib/hobo/authentication_support.rb +1 -1
  23. data/hobo_files/plugin/lib/hobo/bundle.rb +131 -34
  24. data/hobo_files/plugin/lib/hobo/composite_model.rb +1 -1
  25. data/hobo_files/plugin/lib/hobo/controller.rb +7 -8
  26. data/hobo_files/plugin/lib/hobo/dev_controller.rb +21 -0
  27. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +14 -8
  28. data/hobo_files/plugin/lib/hobo/dryml/dryml_support_controller.rb +13 -0
  29. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +6 -7
  30. data/hobo_files/plugin/lib/hobo/dryml/template.rb +207 -73
  31. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +67 -55
  32. data/hobo_files/plugin/lib/hobo/dryml/template_handler.rb +53 -3
  33. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +75 -107
  34. data/hobo_files/plugin/lib/hobo/model.rb +236 -429
  35. data/hobo_files/plugin/lib/hobo/model_controller.rb +277 -437
  36. data/hobo_files/plugin/lib/hobo/model_router.rb +62 -29
  37. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +48 -9
  38. data/hobo_files/plugin/lib/hobo/scopes.rb +98 -0
  39. data/hobo_files/plugin/lib/hobo/scopes/association_proxy_extensions.rb +31 -0
  40. data/hobo_files/plugin/lib/hobo/scopes/automatic_scopes.rb +282 -0
  41. data/hobo_files/plugin/lib/hobo/scopes/defined_scope_proxy_extender.rb +88 -0
  42. data/hobo_files/plugin/lib/hobo/scopes/scope_reflection.rb +18 -0
  43. data/hobo_files/plugin/lib/hobo/scopes/scoped_proxy.rb +59 -0
  44. data/hobo_files/plugin/lib/hobo/undefined.rb +2 -0
  45. data/hobo_files/plugin/lib/hobo/user.rb +31 -14
  46. data/hobo_files/plugin/lib/hobo/user_controller.rb +41 -27
  47. data/hobo_files/plugin/taglibs/core.dryml +9 -11
  48. data/hobo_files/plugin/taglibs/rapid.dryml +51 -108
  49. data/hobo_files/plugin/taglibs/rapid_editing.dryml +25 -25
  50. data/hobo_files/plugin/taglibs/rapid_forms.dryml +111 -79
  51. data/hobo_files/plugin/taglibs/rapid_generics.dryml +74 -0
  52. data/hobo_files/plugin/taglibs/rapid_navigation.dryml +23 -21
  53. data/hobo_files/plugin/taglibs/rapid_pages.dryml +83 -169
  54. data/hobo_files/plugin/taglibs/rapid_plus.dryml +16 -2
  55. data/hobo_files/plugin/taglibs/rapid_support.dryml +3 -3
  56. data/hobo_files/plugin/taglibs/rapid_user_pages.dryml +104 -0
  57. metadata +60 -55
  58. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +0 -276
  59. data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +0 -9
  60. data/hobo_files/plugin/lib/active_record/table_definition.rb +0 -34
  61. data/hobo_files/plugin/lib/extensions.rb +0 -375
  62. data/hobo_files/plugin/lib/hobo/email_address.rb +0 -12
  63. data/hobo_files/plugin/lib/hobo/enum_string.rb +0 -50
  64. data/hobo_files/plugin/lib/hobo/field_declaration_dsl.rb +0 -43
  65. data/hobo_files/plugin/lib/hobo/field_spec.rb +0 -68
  66. data/hobo_files/plugin/lib/hobo/html_string.rb +0 -7
  67. data/hobo_files/plugin/lib/hobo/lazy_hash.rb +0 -40
  68. data/hobo_files/plugin/lib/hobo/markdown_string.rb +0 -11
  69. data/hobo_files/plugin/lib/hobo/migrations.rb +0 -12
  70. data/hobo_files/plugin/lib/hobo/model_queries.rb +0 -117
  71. data/hobo_files/plugin/lib/hobo/password_string.rb +0 -7
  72. data/hobo_files/plugin/lib/hobo/percentage.rb +0 -14
  73. data/hobo_files/plugin/lib/hobo/predicate_dispatch.rb +0 -78
  74. data/hobo_files/plugin/lib/hobo/proc_binding.rb +0 -32
  75. data/hobo_files/plugin/lib/hobo/text.rb +0 -3
  76. data/hobo_files/plugin/lib/hobo/textile_string.rb +0 -25
  77. 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
- load!
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 = Hash.new {|h, k| h[k] = Hash.new {|h, k| h[k] = {} } }
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(subsite, klass, action)
23
- @linkable[subsite][klass.name][action] = true
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?(subsite, klass, action)
27
- @linkable[subsite][klass.name][action]
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
- owner_module = subsite ? module_name.constantize : Object
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
- new_method = Hobo.simple_has_many_association?(model.reflections[collection])
130
- named_route("#{singular}_#{collection}",
131
- "#{plural}/:id/#{collection}",
132
- :action => "show_#{collection}",
133
- :conditions => { :method => :get })
160
+ linkable_route("#{singular}_#{collection}",
161
+ "#{plural}/:id/#{collection}",
162
+ collection.to_s,
163
+ :conditions => { :method => :get })
134
164
 
135
- named_route("new_#{singular}_#{collection.to_s.singularize}",
136
- "#{plural}/:id/#{collection}/new",
137
- :action => "new_#{collection.to_s.singularize}",
138
- :conditions => { :method => :get }) if new_method
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
- named_route("#{plural.singularize}_#{method}", "#{plural}/:id/#{method}",
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
- named_route("#{view}_#{plural}", "#{plural}/#{view}",
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
- named_route("#{plural.singularize}_#{view}", "#{plural}/:id/#{view}",
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
- named_route("#{singular}_login", "#{prefix}login", :action => 'login')
170
- named_route("#{singular}_logout", "#{prefix}logout", :action => 'logout')
171
- named_route("#{singular}_signup", "#{prefix}signup", :action => 'signup')
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 self.class.linkable(subsite, model, action)
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 options_for_hobo_ajax(options)
4
- js_options = build_callbacks(options)
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, message, update, options={})
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, js_str(message || "..."), js_updates(update), js_options].compact
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 => this_field_dom_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
- AJAX_ATTRS = [:before, :success, :failure, :complete, :type, :method,
100
- :script, :form, :params, :confirm,
101
- :reset_form, :refocus_form, :result_update, :spinner_next_to]
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