hobo 0.7.2 → 0.7.3

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