scaffolding_extensions 1.0.0

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 (52) hide show
  1. data/LICENSE +19 -0
  2. data/README +144 -0
  3. data/contrib/scaffold_associations_tree/README +9 -0
  4. data/contrib/scaffold_associations_tree/bullet.gif +0 -0
  5. data/contrib/scaffold_associations_tree/minus.gif +0 -0
  6. data/contrib/scaffold_associations_tree/plus.gif +0 -0
  7. data/contrib/scaffold_associations_tree/scaffold_associations_tree.css +20 -0
  8. data/contrib/scaffold_associations_tree/scaffold_associations_tree.js +57 -0
  9. data/contrib/scaffold_auto_complete_style/README +8 -0
  10. data/contrib/scaffold_auto_complete_style/auto_complete.css +23 -0
  11. data/contrib/scaffold_form_focus/README +12 -0
  12. data/contrib/scaffold_form_focus/scaffold_form_focus.js +21 -0
  13. data/contrib/scaffold_jquery_autocomplete/README +8 -0
  14. data/contrib/scaffold_jquery_autocomplete/jquery.ui.se_autocomplete.css +22 -0
  15. data/contrib/scaffold_jquery_autocomplete/jquery.ui.se_autocomplete.js +121 -0
  16. data/doc/advanced.txt +154 -0
  17. data/doc/camping.txt +25 -0
  18. data/doc/controller_spec.txt +20 -0
  19. data/doc/conversion.txt +102 -0
  20. data/doc/model_spec.txt +54 -0
  21. data/doc/ramaze.txt +20 -0
  22. data/doc/sinatra.txt +20 -0
  23. data/doc/testing.txt +12 -0
  24. data/lib/scaffolding_extensions.rb +89 -0
  25. data/lib/scaffolding_extensions/controller.rb +79 -0
  26. data/lib/scaffolding_extensions/controller/action_controller.rb +116 -0
  27. data/lib/scaffolding_extensions/controller/camping.rb +150 -0
  28. data/lib/scaffolding_extensions/controller/ramaze.rb +116 -0
  29. data/lib/scaffolding_extensions/controller/sinatra.rb +183 -0
  30. data/lib/scaffolding_extensions/helper.rb +304 -0
  31. data/lib/scaffolding_extensions/jquery_helper.rb +58 -0
  32. data/lib/scaffolding_extensions/meta_controller.rb +337 -0
  33. data/lib/scaffolding_extensions/meta_model.rb +571 -0
  34. data/lib/scaffolding_extensions/model.rb +30 -0
  35. data/lib/scaffolding_extensions/model/active_record.rb +184 -0
  36. data/lib/scaffolding_extensions/model/ardm.rb +47 -0
  37. data/lib/scaffolding_extensions/model/data_mapper.rb +190 -0
  38. data/lib/scaffolding_extensions/overridable.rb +67 -0
  39. data/lib/scaffolding_extensions/prototype_helper.rb +59 -0
  40. data/scaffolds/edit.rhtml +12 -0
  41. data/scaffolds/habtm.rhtml +24 -0
  42. data/scaffolds/index.rhtml +6 -0
  43. data/scaffolds/layout.rhtml +82 -0
  44. data/scaffolds/list.rhtml +13 -0
  45. data/scaffolds/listtable.rhtml +46 -0
  46. data/scaffolds/manage.rhtml +15 -0
  47. data/scaffolds/merge.rhtml +23 -0
  48. data/scaffolds/new.rhtml +5 -0
  49. data/scaffolds/search.rhtml +11 -0
  50. data/scaffolds/show.rhtml +19 -0
  51. data/test/scaffolding_extensions_test.rb +44 -0
  52. metadata +106 -0
@@ -0,0 +1,58 @@
1
+ module ScaffoldingExtensions
2
+ # Helper methods that require the JQuery Javascript library to work
3
+ module JQueryHelper
4
+ JS_CHAR_FILTER = {'<'=>'\u003C', '>'=>'\u003E', '"'=>'\\"', "\n"=>'\n'}
5
+
6
+ private
7
+ # Javascript for adding an element to the top of the list of associated records,
8
+ # and setting the autocomplete text box value to blank (if using autocompleting),
9
+ # or removing the item from the select box and showing the blank record instead
10
+ # (if not using autocompleting).
11
+ def scaffold_add_habtm_element
12
+ content = "$('##{@records_list}').prepend(\"#{scaffold_javascript_character_filter(scaffold_habtm_association_line_item(@klass, @association, @record, @associated_record))}\");\n"
13
+ if @auto_complete
14
+ content << "$('##{@element_id}').val('');\n"
15
+ else
16
+ content << "$('##{@element_id}_#{@associated_record.scaffold_id}').remove();\n"
17
+ content << "$('##{@element_id}').selectedIndex = 0;\n"
18
+ end
19
+ content
20
+ end
21
+
22
+ # A form tag with an onsubmit attribute that submits the form to the given url via Ajax
23
+ def scaffold_form_remote_tag(url, options)
24
+ u = scaffold_url(url, options)
25
+ "<form method='post' action='#{u}' onsubmit=\"$.post('#{u}', $(this).serialize(), function(data, textStatus){eval(data);}); return false;\">\n#{scaffold_token_tag}\n"
26
+ end
27
+
28
+ # Javascript that takes the given id as the text box to autocomplete for,
29
+ # submitting the autocomplete request to scaffold_auto_complete_for_#{model_name}
30
+ # (with the association if one is given), using the get method, and displaying values
31
+ # in #{id}_scaffold_auto_complete.
32
+ def scaffold_javascript_autocompleter(id, model_name, association)
33
+ scaffold_javascript_tag("$('##{id}').autocomplete({ajax:'#{scaffold_url("scaffold_auto_complete_for_#{model_name}")}'#{", association:'#{association}'" if association}});")
34
+ end
35
+
36
+ # Filters some html entities and replaces them with their javascript equivalents
37
+ # suitable for use inside a javascript quoted string.
38
+ def scaffold_javascript_character_filter(string)
39
+ string.gsub(/[<>"\n]/){|x| JS_CHAR_FILTER[x]}
40
+ end
41
+
42
+ # Div with link inside that requests the associations html for the @scaffold_object
43
+ # via Ajax and replaces the link with the html returned by the request
44
+ def scaffold_load_associations_with_ajax_link
45
+ soid = @scaffold_object.scaffold_id
46
+ divid = "scaffold_ajax_content_#{soid}"
47
+ "<div id='#{divid}'><a href='#{scaffold_url("edit#{@scaffold_suffix}", :id=>soid, :associations=>:show)}' onclick=\"$('##{divid}').load('#{scaffold_url("associations#{@scaffold_suffix}", :id=>soid)}'); return false;\">Modify Associations</a></div>"
48
+ end
49
+
50
+ # Javascript that removes @remove_element_id from the page and inserts
51
+ # an option into the appropriate select box (unless @auto_complete).
52
+ def scaffold_remove_existing_habtm_element
53
+ content = "$('##{@remove_element_id}').remove();\n"
54
+ content << "$('##{@select_id}').append(\"\\u003Coption value='#{@select_value}' id='#{@select_id}_#{@select_value}'\\u003E#{@select_text}\\u003C/option\\u003E\");\n" unless @auto_complete
55
+ content
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,337 @@
1
+ module ScaffoldingExtensions
2
+ # Contains class methods shared across controllers
3
+ module MetaController
4
+ attr_accessor :scaffolded_methods, :scaffolded_nonidempotent_methods, :scaffolded_auto_complete_associations
5
+
6
+ # The location of the scaffold templates
7
+ def scaffold_template_dir
8
+ @scaffold_template_dir ||= TEMPLATE_DIR
9
+ end
10
+
11
+ private
12
+ # Creates a series of administrative forms for a given model klass. All actions
13
+ # defined have the suffix "_#{klass.scaffold_name}".
14
+ #
15
+ # Takes the following options:
16
+ #
17
+ # - :except: symbol or array of method symbols not to define
18
+ # - :only: symbol or array of method symbols to define instead of the default
19
+ #
20
+ # The following method symbols are used to control the methods that get
21
+ # added by the scaffold function:
22
+ #
23
+ # - :browse: Browse all model objects, similar to a search for every record.
24
+ # - :delete: Shows a select box with all objects (or an autocompleting text box), allowing the user to chose
25
+ # an object to delete
26
+ # - :edit: Shows a select box with all objects (or an autocompleting text box), allowing the user to chose
27
+ # an object to edit. When one is selected, shows a form for changing the fields.
28
+ # Also shows associations specified in the model's scaffold_associations,
29
+ # allowing you easy access to manage associated models and objects.
30
+ # - :manage: Page that has links to all the other methods.
31
+ # - :new: Form for creating new objects
32
+ # - :merge: Brings up two fields (usually select fields), allowing the user to
33
+ # pick one to merge into the other. Merging takes all objects associated with
34
+ # the object to be merged and replaces those associations with associations to
35
+ # the object is it being merged into, and then it deletes the object being merged.
36
+ # - :search: Simple search form. The results page has links to show, edit,
37
+ # or destroy each object.
38
+ # - :show: Shows a select box with all objects (or an autocompleting text box), allowing the user to chose
39
+ # an object, which then shows the attribute name and value for scaffolded fields.
40
+ # Also shows associations specified in the model's scaffold_associations.
41
+ def scaffold(klass, options = {})
42
+ scaffold_setup
43
+ singular_name = klass.scaffold_name
44
+ singular_human_name = klass.scaffold_human_name
45
+ plural_name = singular_name.pluralize
46
+ plural_human_name = singular_human_name.pluralize
47
+ suffix = "_#{singular_name}"
48
+ add_methods = options[:only] ? scaffold_normalize_options(options[:only]) : scaffold_default_methods
49
+ add_methods -= scaffold_normalize_options(options[:except])
50
+ scaffold_options = {:singular_name=>singular_name, :plural_name=>plural_name, :singular_human_name=>singular_human_name, :plural_human_name=>plural_human_name, :class=>klass, :suffix=>suffix, :singular_lc_human_name=>singular_human_name.downcase, :plural_lc_human_name=>plural_human_name.downcase}
51
+
52
+ scaffold_auto_complete_for(klass) if klass.scaffold_use_auto_complete
53
+ klass.scaffold_auto_complete_associations.each{|association| scaffold_auto_complete_for(klass, association)}
54
+
55
+ if add_methods.include?(:manage)
56
+ scaffold_define_method("manage#{suffix}") do
57
+ scaffold_render_template(:manage, scaffold_options)
58
+ end
59
+ end
60
+
61
+ if add_methods.include?(:show) or add_methods.include?(:destroy) or add_methods.include?(:edit)
62
+ scaffold_define_method("list#{suffix}") do
63
+ @scaffold_objects ||= klass.scaffold_find_objects(@scaffold_action, :session=>scaffold_session) unless klass.scaffold_use_auto_complete
64
+ scaffold_render_template(:list, scaffold_options)
65
+ end
66
+ end
67
+
68
+ if add_methods.include?(:show)
69
+ scaffold_define_method("show#{suffix}") do
70
+ if scaffold_request_id
71
+ @scaffold_object ||= klass.scaffold_find_object(:show, scaffold_request_id, :session=>scaffold_session)
72
+ @scaffold_associations_readonly = true
73
+ scaffold_render_template(:show, scaffold_options)
74
+ else
75
+ @scaffold_action = :show
76
+ send("list#{suffix}")
77
+ end
78
+ end
79
+ end
80
+
81
+ if add_methods.include?(:delete)
82
+ scaffold_define_method("delete#{suffix}") do
83
+ @scaffold_action = :destroy
84
+ send("list#{suffix}")
85
+ end
86
+
87
+ scaffold_define_nonidempotent_method("destroy#{suffix}") do
88
+ @scaffold_object ||= klass.scaffold_find_object(:delete, scaffold_request_id, :session=>scaffold_session)
89
+ klass.scaffold_destroy(@scaffold_object)
90
+ scaffold_redirect('delete', suffix, "#{singular_human_name} was successfully destroyed")
91
+ end
92
+ end
93
+
94
+ if add_methods.include?(:edit)
95
+ klass.scaffold_habtm_associations.each{|association| scaffold_habtm(klass, association)}
96
+
97
+ scaffold_define_method("edit#{suffix}") do
98
+ if scaffold_request_id
99
+ @scaffold_show_associations = true if scaffold_request_param(:associations) == 'show'
100
+ @scaffold_object ||= klass.scaffold_find_object(:edit, scaffold_request_id, :session=>scaffold_session)
101
+ scaffold_render_template(:edit, scaffold_options)
102
+ else
103
+ @scaffold_action = :edit
104
+ send("list#{suffix}")
105
+ end
106
+ end
107
+
108
+ scaffold_define_nonidempotent_method("update#{suffix}") do
109
+ @scaffold_object ||= klass.scaffold_find_object(:edit, scaffold_request_id, :session=>scaffold_session)
110
+ klass.scaffold_update_attributes(@scaffold_object, scaffold_request_param(singular_name))
111
+ if klass.scaffold_save(:edit, @scaffold_object)
112
+ scaffold_redirect(:edit, suffix, "#{singular_human_name} was successfully updated")
113
+ else
114
+ scaffold_render_template(:edit, scaffold_options)
115
+ end
116
+ end
117
+
118
+ if klass.scaffold_load_associations_with_ajax
119
+ scaffold_define_method("associations#{suffix}") do
120
+ @scaffold_object ||= klass.scaffold_find_object(:edit, scaffold_request_id, :session=>scaffold_session)
121
+ scaffold_render_template(:associations, scaffold_options, :inline=>"<%= scaffold_habtm_ajax_associations %>\n<%= scaffold_association_links %>\n")
122
+ end
123
+ end
124
+ end
125
+
126
+ if add_methods.include?(:new)
127
+ scaffold_define_method("new#{suffix}") do
128
+ @scaffold_object ||= klass.scaffold_new_object(scaffold_request_param(singular_name), :session=>scaffold_session)
129
+ scaffold_render_template(:new, scaffold_options)
130
+ end
131
+
132
+ scaffold_define_nonidempotent_method("create#{suffix}") do
133
+ @scaffold_object ||= klass.scaffold_new_object(scaffold_request_param(singular_name), :session=>scaffold_session)
134
+ if klass.scaffold_save(:new, @scaffold_object)
135
+ scaffold_redirect(:new, suffix, "#{singular_human_name} was successfully created")
136
+ else
137
+ scaffold_render_template(:new, scaffold_options)
138
+ end
139
+ end
140
+ end
141
+
142
+ if add_methods.include?(:search)
143
+ scaffold_define_method("search#{suffix}") do
144
+ @scaffold_object ||= klass.scaffold_search_object
145
+ scaffold_render_template(:search, scaffold_options)
146
+ end
147
+
148
+ scaffold_define_method("results#{suffix}") do
149
+ page = scaffold_request_param(:page).to_i > 1 ? scaffold_request_param(:page).to_i : 1
150
+ page -= 1 if scaffold_request_param(:page_previous)
151
+ page += 1 if scaffold_request_param(:page_next)
152
+ @scaffold_search_results_form_params, @scaffold_objects = klass.scaffold_search(:model=>scaffold_request_param(singular_name), :notnull=>scaffold_force_array(scaffold_request_param(:notnull)), :null=>scaffold_force_array(scaffold_request_param(:null)), :page=>page, :session=>scaffold_session)
153
+ @scaffold_listtable_type = :search
154
+ scaffold_render_template(:listtable, scaffold_options)
155
+ end
156
+ end
157
+
158
+ if add_methods.include?(:merge)
159
+ scaffold_define_method("merge#{suffix}") do
160
+ @scaffold_objects ||= klass.scaffold_find_objects(:merge, :session=>scaffold_session) unless klass.scaffold_use_auto_complete
161
+ scaffold_render_template(:merge, scaffold_options)
162
+ end
163
+
164
+ scaffold_define_nonidempotent_method("merge_update#{suffix}") do
165
+ notice = if klass.scaffold_merge_records(scaffold_request_param(:from), scaffold_request_param(:to), :session=>scaffold_session)
166
+ "#{plural_human_name} were successfully merged"
167
+ else
168
+ "Error merging #{plural_human_name.downcase}"
169
+ end
170
+ scaffold_redirect(:merge, suffix, notice)
171
+ end
172
+ end
173
+
174
+ if add_methods.include?(:browse)
175
+ scaffold_define_method("browse#{suffix}") do
176
+ @page ||= scaffold_request_param(:page).to_i > 1 ? scaffold_request_param(:page).to_i : 1
177
+ @next_page, @scaffold_objects = klass.scaffold_browse_find_objects(:session=>scaffold_session, :page=>@page)
178
+ @scaffold_listtable_type = :browse
179
+ scaffold_render_template(:listtable, scaffold_options)
180
+ end
181
+ end
182
+ end
183
+
184
+ # Scaffolds all models with one command
185
+ #
186
+ # Takes the following options:
187
+ # - :except: model class or array of model classes not to scaffold
188
+ # - :only: only scaffold model classes included in this array
189
+ # - model class: hash of options for the scaffold method for this model
190
+ def scaffold_all_models(options={})
191
+ scaffold_setup
192
+ links = scaffold_all_models_parse_options(options).collect do |model, options|
193
+ scaffold(model, options)
194
+ ["manage_#{model.scaffold_name}", model.scaffold_human_name]
195
+ end
196
+ scaffold_define_method('index') do
197
+ @links = links
198
+ scaffold_render_template('index')
199
+ end
200
+ end
201
+
202
+ # Parse the arguments for scaffold_all_models. Seperated so that it can
203
+ # also be used in testing.
204
+ def scaffold_all_models_parse_options(options={})
205
+ except = scaffold_normalize_options(options[:except])
206
+ only = scaffold_normalize_options(options[:only]) if options[:only]
207
+ models = (only || ScaffoldingExtensions.all_models).reject{|model| except.include?(model)}
208
+ models.collect{|model| [model, options[model] || {}]}
209
+ end
210
+
211
+ # Create action for returning results from the scaffold autocompleter
212
+ # for the given model. If an association is given, allows autocompleting for
213
+ # objects in an association.
214
+ def scaffold_auto_complete_for(klass, association=nil)
215
+ meth = "scaffold_auto_complete_for_#{klass.scaffold_name}"
216
+ (self.scaffolded_auto_complete_associations[klass] ||= Set.new).add(association.to_s)
217
+ allowed_associations = scaffolded_auto_complete_associations[klass]
218
+ unless instance_methods.include?(meth)
219
+ scaffold_define_method(meth) do
220
+ association = scaffold_request_param(:association).to_s
221
+ if scaffold_request_param(:association) && !allowed_associations.include?(association)
222
+ scaffold_method_not_allowed
223
+ else
224
+ @items = klass.scaffold_auto_complete_find(scaffold_request_id.to_s, :association=>(association.to_sym if scaffold_request_param(:association)), :session=>scaffold_session)
225
+ scaffold_render_template(meth, {}, :inline => "<%= scaffold_auto_complete_result(@items) %>")
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ # The methods that should be added by the scaffolding function by default
232
+ def scaffold_default_methods
233
+ @scaffold_default_methods ||= DEFAULT_METHODS
234
+ end
235
+
236
+ # Define method and add method name to scaffolded_methods
237
+ def scaffold_define_method(name, &block)
238
+ scaffolded_methods.add(name)
239
+ define_method(name, &block)
240
+ end
241
+
242
+ # Define method and add method name to scaffolded_methods and
243
+ # scaffolded_nonidempotent_methods
244
+ def scaffold_define_nonidempotent_method(name, &block)
245
+ scaffolded_nonidempotent_methods.add(name)
246
+ scaffold_define_method(name, &block)
247
+ end
248
+
249
+ # Scaffolds a habtm association for a class and an association using two select boxes, or
250
+ # a select box for removing associations and an autocompleting text box for
251
+ # adding associations.
252
+ def scaffold_habtm(klass, association)
253
+ scaffold_setup
254
+ sn = klass.scaffold_name
255
+ scaffold_auto_complete_for(klass, association) if auto_complete = klass.scaffold_association_use_auto_complete(association)
256
+
257
+ if klass.scaffold_habtm_with_ajax
258
+ suffix = "_#{sn}"
259
+ records_list = "#{sn}_associated_records_list"
260
+ element_id = "#{sn}_#{association}_id"
261
+ add_meth = "add_#{association}_to_#{sn}"
262
+ scaffold_define_nonidempotent_method(add_meth) do
263
+ @record = klass.scaffold_find_object(:habtm, scaffold_request_id, :session=>scaffold_session)
264
+ @associated_record = klass.scaffold_add_associated_objects(association, @record, {:session=>scaffold_session}, scaffold_request_param(element_id))
265
+ if scaffold_xhr?
266
+ @klass = klass
267
+ @association = association
268
+ @records_list = records_list
269
+ @auto_complete = auto_complete
270
+ @element_id = element_id
271
+ @scaffold_javascript = true
272
+ scaffold_render_template(add_meth, {}, :inline=>'<%= scaffold_add_habtm_element %>')
273
+ else
274
+ scaffold_redirect_to(scaffold_url("edit#{suffix}", :id=>@record.scaffold_id))
275
+ end
276
+ end
277
+
278
+ remove_meth = "remove_#{association}_from_#{sn}"
279
+ scaffold_define_nonidempotent_method(remove_meth) do
280
+ @record = klass.scaffold_find_object(:habtm, scaffold_request_id, :session=>scaffold_session)
281
+ @associated_record = klass.scaffold_remove_associated_objects(association, @record, {:session=>scaffold_session}, scaffold_request_param(element_id))
282
+ @auto_complete = auto_complete
283
+ if scaffold_xhr?
284
+ @remove_element_id = "#{sn}_#{@record.scaffold_id}_#{association}_#{@associated_record.scaffold_id}"
285
+ @select_id = element_id
286
+ @select_value = @associated_record.scaffold_id
287
+ @select_text = @associated_record.scaffold_name
288
+ @scaffold_javascript = true
289
+ scaffold_render_template(remove_meth, {}, :inline=>'<%= scaffold_remove_existing_habtm_element %>')
290
+ else
291
+ scaffold_redirect_to(scaffold_url("edit#{suffix}", :id=>@record.scaffold_id))
292
+ end
293
+ end
294
+ else
295
+ suffix = "_#{sn}_#{association}"
296
+ # aplch_name = association plural lower case human name
297
+ scaffold_options={:aplch_name=>association.to_s.humanize.downcase, :singular_name=>sn, :association=>association, :class=>klass, :suffix=>suffix}
298
+ # aslch_name = association singular lower case human name
299
+ scaffold_options[:aslhc_name] = scaffold_options[:aplch_name].singularize
300
+
301
+ scaffold_define_method("edit#{suffix}") do
302
+ @scaffold_object = klass.scaffold_find_object(:habtm, scaffold_request_id, :session=>scaffold_session, :association=>association)
303
+ @items_to_remove = klass.scaffold_associated_objects(association, @scaffold_object, :session=>scaffold_session)
304
+ @items_to_add = klass.scaffold_unassociated_objects(association, @scaffold_object, :session=>scaffold_session) unless klass.scaffold_association_use_auto_complete(association)
305
+ scaffold_render_template(:habtm, scaffold_options)
306
+ end
307
+
308
+ scaffold_define_nonidempotent_method("update#{suffix}") do
309
+ @scaffold_object = klass.scaffold_find_object(:habtm, scaffold_request_id, :session=>scaffold_session, :association=>association)
310
+ klass.scaffold_add_associated_objects(association, @scaffold_object, {:session=>scaffold_session}, *scaffold_select_ids(scaffold_request_param(:add)))
311
+ klass.scaffold_remove_associated_objects(association, @scaffold_object, {:session=>scaffold_session}, *scaffold_select_ids(scaffold_request_param(:remove)))
312
+ scaffold_redirect(:edit, suffix, "Updated #{@scaffold_object.scaffold_name}'s #{scaffold_options[:aplch_name]} successfully", @scaffold_object.scaffold_id)
313
+ end
314
+ end
315
+ end
316
+
317
+ # Normalizes options to the scaffold command, allowing submission of symbols or arrays of symbols
318
+ def scaffold_normalize_options(options)
319
+ case options
320
+ when Array then options
321
+ when nil then []
322
+ else [options]
323
+ end
324
+ end
325
+
326
+ # Setup resources used by both scaffold and scaffold_habtm. Adds shared data structures that
327
+ # should only be initialized once.
328
+ def scaffold_setup
329
+ return if @scaffolding_shared_resources_are_setup
330
+ self.scaffolded_methods ||= Set.new
331
+ self.scaffolded_nonidempotent_methods ||= Set.new
332
+ self.scaffolded_auto_complete_associations ||= {}
333
+ scaffold_setup_helper
334
+ @scaffolding_shared_resources_are_setup = true
335
+ end
336
+ end
337
+ end
@@ -0,0 +1,571 @@
1
+ # Class methods shared by all models
2
+ #
3
+ # Many class methods can be overridden for particular use cases by setting other methods
4
+ # or instance variables. For example, scaffold_fields is a method that takes an action,
5
+ # such as :new. scaffold_fields(:new) will first check if scaffold_new_fields is a method, and
6
+ # will call it if so. If not, it will check if @scaffold_new_fields is defined, and use it if
7
+ # so. If not, will take the default course of action.
8
+ #
9
+ # The first argument to the overridable method is used to search for the override method
10
+ # or instance variable. If a override method is used, the remaining
11
+ # arguments to the overridable method will be passed to it. Otherwise, the default method will
12
+ # be used.
13
+ #
14
+ # For example, scaffold_find_object(action, id, options) is an overridable method.
15
+ # That means if :delete is the action, it will first look for scaffold_delete_find_object, and if it
16
+ # exists it will call scaffold_delete_find_object(id, options).
17
+ #
18
+ # If a method can be overridden by an instance variable, it should have only one argument.
19
+ #
20
+ # The methods that are overridable by other methods are (without the "scaffold_" prefix):
21
+ # add_associated_objects, associated_objects, association_find_object, association_find_objects,
22
+ # find_object, find_objects, new_associated_object_values, remove_associated_objects, save,
23
+ # unassociated_objects, and filter_attributes.
24
+ #
25
+ # The methods that are overridable by other methods or instance variables are (again, without the
26
+ # "scaffold_" prefix): associated_human_name, association_use_auto_complete, fields, include,
27
+ # select_order, attributes, include_association, and select_order_association.
28
+ #
29
+ # Most methods that find objects check options[:session][scaffold_session_value] as a
30
+ # security check if scaffold_session_value is set.
31
+ #
32
+ # There are some methods that are so similar that they are dynamically defined using
33
+ # define_method. They are:
34
+ #
35
+ # - scaffold_association_list_class: The html class attribute of the association list
36
+ # - scaffold_habtm_with_ajax: Whether to use Ajax when scaffolding habtm associations for this model
37
+ # - scaffold_load_associations_with_ajax: Whether to use Ajax when loading associations on the edit page
38
+ # - scaffold_browse_records_per_page: The number of records to show on each page when using the browse scaffold
39
+ # - scaffold_convert_text_to_string: Whether to use input of type text instead of a text area for columns of type :text
40
+ # - scaffold_search_results_limit: The maximum number of results to show on the scaffolded search results page
41
+ #
42
+ # Each of these methods can be set with an instance variable. If the instance variable is
43
+ # not set, it will use the default value from SCAFFOLD_OPTIONS.
44
+ module ScaffoldingExtensions::MetaModel
45
+ # Sets the default options for all models, which can be overriden by class instance
46
+ # variables (iv):
47
+ # - :text_to_string: If true, by default, use input type text instead of textarea
48
+ # for fields of type text (iv: @scaffold_convert_text_to_string)
49
+ # - :table_classes: Set the default table classes for different scaffolded HTML tables
50
+ # (iv: @scaffold_table_classes)
51
+ # - :column_types: Override the default column type for a given attribute
52
+ # (iv: @scaffold_column_types)
53
+ # - :column_options: Override the default column options for a given attribute
54
+ # (iv: @scaffold_column_options_hash)
55
+ # - :column_names: Override the visible names of columns for each attribute
56
+ # (iv: @scaffold_column_names)
57
+ # - :association_list_class: Override the html class for the association list in the edit view
58
+ # (iv: @scaffold_association_list_class)
59
+ # - :browse_limit: The default number of records per page to show in the
60
+ # browse scaffold. If nil, the search results will be displayed on one page instead
61
+ # of being paginated (iv: @scaffold_browse_records_per_page)
62
+ # - :search_limit: The default limit on scaffolded search results. If nil,
63
+ # the search results will be displayed on one page instead of being paginated
64
+ # (iv: @scaffold_search_results_limit)
65
+ # - :habtm_ajax: Whether or not to use Ajax (instead of a separate page) for
66
+ # modifying habtm associations (iv: @scaffold_habtm_with_ajax)
67
+ # - :load_associations_ajax - Whether or not to use Ajax to load the display of
68
+ # associations on the edit page, useful if the default display is too slow
69
+ # (iv: @scaffold_load_associations_with_ajax)
70
+ # - :auto_complete: Hash containing the default options to use for the scaffold
71
+ # autocompleter (iv: @scaffold_auto_complete_options)
72
+ SCAFFOLD_OPTIONS = {:text_to_string=>false,
73
+ :table_classes=>{:form=>'formtable', :list=>'sortable', :show=>'sortable'},
74
+ :column_types=>{},
75
+ :column_options=>{},
76
+ :association_list_class=>''.freeze,
77
+ :column_names=>{},
78
+ :browse_limit=>10,
79
+ :search_limit=>10,
80
+ :habtm_ajax=>false,
81
+ :load_associations_ajax=>false,
82
+ :auto_complete=>{:enable=>false, :sql_name=>'LOWER(name)', :format_string=>:substring,
83
+ :search_operator=>'LIKE', :results_limit=>10, :phrase_modifier=>:downcase},
84
+ }
85
+
86
+ {:association_list_class=>:scaffold_association_list_class,
87
+ :habtm_ajax=>:scaffold_habtm_with_ajax,
88
+ :load_associations_ajax=>:scaffold_load_associations_with_ajax,
89
+ :browse_limit=>:scaffold_browse_records_per_page,
90
+ :text_to_string=>:scaffold_convert_text_to_string,
91
+ :search_limit=>:scaffold_search_results_limit}.each do |default, iv|
92
+ ivs = "@#{iv}"
93
+ define_method(iv) do
94
+ if instance_variable_defined?(ivs)
95
+ instance_variable_get(ivs)
96
+ else
97
+ instance_variable_set(ivs, SCAFFOLD_OPTIONS[default])
98
+ end
99
+ end
100
+ end
101
+
102
+ # Control access to objects of this model via a session value. For example
103
+ # if set to :user_id, would only show objects with the same user_id as
104
+ # session[:user_id], and would not allow access to any objects that didn't
105
+ # have a matching user_id.
106
+ attr_reader :scaffold_session_value
107
+
108
+ # Override the typical table-based form display by overriding these wrappers.
109
+ # Should return a proc that takes an html label and
110
+ # widget and returns an html fragment.
111
+ attr_reader :scaffold_field_wrapper
112
+
113
+ # Override the typical table-based form display by overriding these wrappers.
114
+ # Should return a proc that takes rows returned by
115
+ # scaffold_field_wrapper and returns an html fragment.
116
+ attr_reader :scaffold_form_wrapper
117
+
118
+ # Add the objects specified by associated_object_ids to the given object
119
+ # using the given association. If the object to be associated with the given object is
120
+ # already associated with it, skip it (don't try to associate it multiple times).
121
+ # Returns the associated object (if only one id was given), or an array of objects
122
+ # (if multiple ids were given).
123
+ def scaffold_add_associated_objects(association, object, options, *associated_object_ids)
124
+ unless associated_object_ids.empty?
125
+ scaffold_transaction do
126
+ associated_objects = associated_object_ids.collect do |associated_object_id|
127
+ associated_object = scaffold_association_find_object(association, associated_object_id.to_i, :session=>options[:session])
128
+ scaffold_add_associated_object(association, object, associated_object)
129
+ associated_object
130
+ end
131
+ associated_object_ids.length == 1 ? associated_objects.first : associated_objects
132
+ end
133
+ end
134
+ end
135
+
136
+ # Return the human name for the given association, defaulting to humanizing the
137
+ # association name
138
+ def scaffold_associated_human_name(association)
139
+ association.to_s.humanize
140
+ end
141
+
142
+ # The scaffold_name of the associated class. Not overridable, as that allows for the
143
+ # possibility of broken links.
144
+ def scaffold_associated_name(association)
145
+ scaffold_associated_class(association).scaffold_name
146
+ end
147
+
148
+ # All objects that are currently associated with the given object. This method does not
149
+ # check that the returned associated objects meet the associated class's scaffold_session_value
150
+ # constraint, as it is assumed that all objects currently assocated with the given object
151
+ # have already met the criteria. If that is not the case, you should override this method.
152
+ def scaffold_associated_objects(association, object, options)
153
+ object.send(association)
154
+ end
155
+
156
+ # Finds a given object in the associated class that has the matching id.
157
+ def scaffold_association_find_object(association, id, options)
158
+ scaffold_associated_class(association).scaffold_find_object(:associated, id, options)
159
+ end
160
+
161
+ # Find all objects of the associated class. Does not use any conditions of the association
162
+ # (they are can't be used reliably, since they require an object to interpolate them), so
163
+ # if there are special conditions on the association, you'll want to override this method.
164
+ def scaffold_association_find_objects(association, options)
165
+ klass = scaffold_associated_class(association)
166
+ klass.scaffold_get_objects(:order=>scaffold_select_order_association(association), :include=>scaffold_include_association(association), :conditions=>klass.scaffold_session_conditions(options[:session]))
167
+ end
168
+
169
+ # Whether to use autocompleting for linked associations. Defaults to whether the
170
+ # associated class uses auto completing.
171
+ def scaffold_association_use_auto_complete(association)
172
+ scaffold_associated_class(association).scaffold_use_auto_complete
173
+ end
174
+
175
+ # Defaults to associations specified by scaffold fields that are autocompleting. Can be set with an instance variable.
176
+ def scaffold_auto_complete_associations
177
+ @scaffold_auto_complete_associations ||= scaffold_fields(:edit).reject{|field| !(scaffold_association(field) && scaffold_association_use_auto_complete(field))}
178
+ end
179
+
180
+ # Return all records that match the given phrase (usually a substring of
181
+ # the most important column). If options[:association] is present, delegate to the associated
182
+ # class's scaffold_auto_complete_find.
183
+ #
184
+ # Allows method override (options.delete(:association))
185
+ def scaffold_auto_complete_find(phrase, options = {})
186
+ session = options.delete(:session)
187
+ if association = options.delete(:association)
188
+ if meth = scaffold_method_override(:auto_complete_find, association, phrase, options)
189
+ meth.call
190
+ else
191
+ scaffold_associated_class(association).scaffold_auto_complete_find(phrase, :session=>session)
192
+ end
193
+ else
194
+ find_options = { :limit => scaffold_auto_complete_results_limit,
195
+ :conditions => [scaffold_auto_complete_conditions(phrase), scaffold_session_conditions(session)],
196
+ :order => scaffold_select_order(:auto_complete),
197
+ :include => scaffold_include(:auto_complete)}.merge(options)
198
+ scaffold_get_objects(find_options)
199
+ end
200
+ end
201
+
202
+ # Separate method for browsing objects, as it also needs to return whether or not there is another
203
+ # page of objects. Returns [another_page, objects], where another_page is true or false.
204
+ def scaffold_browse_find_objects(options)
205
+ get_options = {:order=>scaffold_select_order(:browse), :include=>scaffold_include(:browse), :conditions=>scaffold_session_conditions(options[:session])}
206
+ if limit = scaffold_browse_records_per_page
207
+ get_options[:offset] = (options[:page].to_i-1) * limit
208
+ get_options[:limit] = limit = limit + 1
209
+ end
210
+ objects = scaffold_get_objects(get_options)
211
+ if limit && objects.length == limit
212
+ objects.pop
213
+ [true, objects]
214
+ else
215
+ [false, objects]
216
+ end
217
+ end
218
+
219
+ # Returns the human name for a given attribute. Can be set via the instance variable
220
+ # @scaffold_column_names, a hash with the column name as a symbol key and the human name
221
+ # string as the value.
222
+ def scaffold_column_name(column_name)
223
+ @scaffold_column_names ||= SCAFFOLD_OPTIONS[:column_names].dup
224
+ @scaffold_column_names[column_name] ||= if scaffold_association(column_name)
225
+ scaffold_associated_human_name(column_name)
226
+ else
227
+ column_name.to_s.humanize
228
+ end
229
+ end
230
+
231
+ # Returns any special options for a given attribute. Can be set via the instance variable
232
+ # @scaffold_column_options_hash, a hash with the column name as a symbol key and the html
233
+ # options hash as the value.
234
+ def scaffold_column_options(column_name)
235
+ @scaffold_column_options_hash ||= SCAFFOLD_OPTIONS[:column_options].dup
236
+ @scaffold_column_options_hash[column_name] || {}
237
+ end
238
+
239
+ # Returns the column type for the given scaffolded column name. Can be set via the instance
240
+ # variable @scaffold_column_types, a hash with the column name as a symbol key and the html
241
+ # type symbol as a value. Associations have the :association type, and other types are looked
242
+ # up via columns_hash[column_name].type. If no type is provided, :string is used by default.
243
+ def scaffold_column_type(column_name)
244
+ @scaffold_column_types ||= SCAFFOLD_OPTIONS[:column_types].dup
245
+ if @scaffold_column_types[column_name]
246
+ @scaffold_column_types[column_name]
247
+ elsif scaffold_association(column_name)
248
+ :association
249
+ elsif type = scaffold_table_column_type(column_name)
250
+ (scaffold_convert_text_to_string && (type == :text)) ? :string : type
251
+ else
252
+ :string
253
+ end
254
+ end
255
+
256
+ # Returns the foreign key for the field if it is an association, or the field
257
+ # as a string if it is not.
258
+ def scaffold_field_id(field)
259
+ if reflection = scaffold_association(field)
260
+ scaffold_foreign_key(reflection)
261
+ else
262
+ field.to_s
263
+ end
264
+ end
265
+
266
+ # Find the object of this model given by the id
267
+ def scaffold_find_object(action, id, options)
268
+ object = scaffold_get_object(id)
269
+ raise scaffold_error_raised unless scaffold_session_value_matches?(object, options[:session])
270
+ object
271
+ end
272
+
273
+ # Find all objects of this model
274
+ def scaffold_find_objects(action, options)
275
+ scaffold_get_objects(:order=>scaffold_select_order(action), :include=>scaffold_include(action), :conditions=>scaffold_session_conditions(options[:session]))
276
+ end
277
+
278
+ # Array of symbols for all habtm associations in this model's scaffold_associations.
279
+ # Can be set with an instance variable.
280
+ def scaffold_habtm_associations
281
+ @scaffold_habtm_associations ||= scaffold_associations.reject{|association| scaffold_association_type(association) != :edit}
282
+ end
283
+
284
+ # The human name string for this model. Can be set with an instance variable.
285
+ def scaffold_human_name
286
+ @scaffold_human_name ||= scaffold_name.humanize
287
+ end
288
+
289
+ # The name string to use in urls, defaults to name.underscore. Can be set with an
290
+ # instance variable.
291
+ def scaffold_name
292
+ @scaffold_name ||= name.underscore
293
+ end
294
+
295
+ # Merges the record with id from into the record with id to. Updates all
296
+ # associated records for the record with id from to be assocatiated with
297
+ # the record with id to instead, and then deletes the record with id from.
298
+ #
299
+ # Returns false if the ids given are the same or the scaffold_session_value
300
+ # criteria is not met.
301
+ def scaffold_merge_records(from, to, options)
302
+ from, to = from.to_i, to.to_i
303
+ return false if from == to
304
+ from_object = scaffold_get_object(from)
305
+ return false unless scaffold_session_value_matches?(from_object, options[:session])
306
+ to_object = scaffold_get_object(to)
307
+ return false unless scaffold_session_value_matches?(to_object, options[:session])
308
+ scaffold_transaction do
309
+ scaffold_all_associations.each{|reflection| scaffold_reflection_merge(reflection, from, to)}
310
+ scaffold_destroy(from_object)
311
+ end
312
+ true
313
+ end
314
+
315
+ # Creates a new object, setting the attributes if given.
316
+ def scaffold_new_object(attributes, options)
317
+ object = new(scaffold_filter_attributes(:new, attributes || {}))
318
+ object.send("#{scaffold_session_value}=", options[:session][scaffold_session_value]) if scaffold_session_value
319
+ object
320
+ end
321
+
322
+ # Removes associated objects with the given ids from the given object's association.
323
+ # Returns the associated object (if only one id was given), or an array of objects
324
+ # (if multiple ids were given).
325
+ def scaffold_remove_associated_objects(association, object, options, *associated_object_ids)
326
+ unless associated_object_ids.empty?
327
+ scaffold_transaction do
328
+ associated_objects = associated_object_ids.collect do |associated_object_id|
329
+ associated_object = scaffold_association_find_object(association, associated_object_id.to_i, :session=>options[:session])
330
+ scaffold_remove_associated_object(association, object, associated_object)
331
+ associated_object
332
+ end
333
+ associated_object_ids.length == 1 ? associated_objects.first : associated_objects
334
+ end
335
+ end
336
+ end
337
+
338
+ # Searches for objects that meet the criteria specified by options:
339
+ # - :null: fields that must be must be NULL
340
+ # - :notnull: matching fields must be NOT NULL
341
+ # - :model: hash with field name strings as keys and strings as values.
342
+ # uses the value for each field to search. Strings are searched based on
343
+ # substring, other values have to be an exact match.
344
+ # - :page: To determine which offset to use
345
+ #
346
+ # Returns [form_params, objects], where form_params are a list of parameters
347
+ # for the results form (for going to the next/previous page), and objects are
348
+ # all of the objects that matched.
349
+ def scaffold_search(options)
350
+ conditions = []
351
+ search_model = options[:model] || {}
352
+ object = scaffold_search_object(search_model)
353
+ null = options[:null]
354
+ notnull = options[:notnull]
355
+ form_params= {:model=>{}, :null=>[], :notnull=>[], :page=>options[:page]}
356
+
357
+ limit, offset = nil, nil
358
+ if scaffold_search_pagination_enabled?
359
+ limit = scaffold_search_results_limit + 1
360
+ offset = options[:page] > 1 ? (limit-1)*(options[:page] - 1) : nil
361
+ end
362
+
363
+ if search_model
364
+ scaffold_attributes(:search).each do |field|
365
+ fsym = field
366
+ field = field.to_s
367
+ next if (null && null.include?(field)) || \
368
+ (notnull && notnull.include?(field)) || \
369
+ search_model[field].nil? || search_model[field].empty?
370
+ case scaffold_column_type(fsym)
371
+ when :string, :text
372
+ conditions << scaffold_substring_condition(field, object.send(field))
373
+ else
374
+ conditions << scaffold_equal_condition(field, object.send(field))
375
+ end
376
+ form_params[:model][field] = search_model[field] if scaffold_search_pagination_enabled?
377
+ end
378
+ end
379
+
380
+ scaffold_attributes(:search).each do |field|
381
+ field = field.to_s
382
+ if null && null.include?(field)
383
+ conditions << scaffold_null_condition(field)
384
+ form_params[:null] << field if scaffold_search_pagination_enabled?
385
+ end
386
+ if notnull && notnull.include?(field)
387
+ conditions << scaffold_notnull_condition(field)
388
+ form_params[:notnull] << field if scaffold_search_pagination_enabled?
389
+ end
390
+ end
391
+
392
+ conditions << scaffold_session_conditions(options[:session])
393
+
394
+ objects = scaffold_get_objects(:conditions=>conditions, :include=>scaffold_include(:search), :order=>scaffold_select_order(:search), :limit=>limit, :offset=>offset)
395
+ if scaffold_search_pagination_enabled? && objects.length == scaffold_search_results_limit+1
396
+ form_params[:next_page] = true
397
+ objects.pop
398
+ end
399
+ [form_params, objects]
400
+ end
401
+
402
+ # List of human visible names and field name symbols to use for NULL/NOT NULL fields on the scaffolded search page.
403
+ # Can be set with an instance variable.
404
+ def scaffold_search_null_options
405
+ @scaffold_search_null_options ||= scaffold_attributes(:search).reject{|f| scaffold_table_column_type(f).nil?}.collect{|f| [scaffold_column_name(f), f]}
406
+ end
407
+
408
+ # Returns a completely blank object suitable for searching, updated with the given attributes.
409
+ def scaffold_search_object(attributes = {})
410
+ object = new
411
+ scaffold_attributes(:search).each{|field| object.send("#{field}=", nil)}
412
+ scaffold_set_attributes(object, attributes)
413
+ object
414
+ end
415
+
416
+ # The SQL ORDER BY fragment string. Can be set with an instance variable.
417
+ def scaffold_select_order(action = :default)
418
+ @scaffold_select_order
419
+ end
420
+
421
+ # The conditions array to use if scaffold_session_value is set, nil otherwise
422
+ def scaffold_session_conditions(session)
423
+ ["#{scaffold_table_name}.#{scaffold_session_value} = ?", session[scaffold_session_value]] if scaffold_session_value
424
+ end
425
+
426
+ # True if the given object meets the scaffold_session_value criteria
427
+ def scaffold_session_value_matches?(object, session)
428
+ !scaffold_session_value || object.send(scaffold_session_value) == session[scaffold_session_value]
429
+ end
430
+
431
+ # Whether to show associations links for the given association. Generally true unless
432
+ # it is an :has_and_belongs_to_many association and scaffold_habtm_with_ajax is true.
433
+ def scaffold_show_association_links?(association)
434
+ !(scaffold_habtm_with_ajax && scaffold_association_type(association) == :edit)
435
+ end
436
+
437
+ # Returns the scaffolded table class for a given scaffold type. Can be set with
438
+ # the instance variable @scaffold_table_classes, a hash with the type as the symbol key
439
+ # and the value as the html class string.
440
+ def scaffold_table_class(type)
441
+ @scaffold_table_classes ||= SCAFFOLD_OPTIONS[:table_classes].dup
442
+ @scaffold_table_classes[type]
443
+ end
444
+
445
+ # Run the block inside a database transaction
446
+ def scaffold_transaction(&block)
447
+ transaction(&block)
448
+ end
449
+
450
+ # Returns all objects of the associated class not currently associated with this object.
451
+ def scaffold_unassociated_objects(association, object, options)
452
+ scaffold_associated_class(association).scaffold_get_objects(:conditions=>[scaffold_unassociated_condition(association, object), scaffold_session_conditions(options[:session])], :order=>scaffold_select_order_association(association), :include=>scaffold_include_association(association))
453
+ end
454
+
455
+ # Updates attributes for the given action, but does not save the record.
456
+ def scaffold_update_attributes(object, attributes)
457
+ scaffold_set_attributes(object, scaffold_filter_attributes(:edit, attributes))
458
+ end
459
+
460
+ # Whether this class should use an autocompleting text box instead of a select
461
+ # box for choosing items. Can be set with an instance variable.
462
+ def scaffold_use_auto_complete
463
+ @scaffold_use_auto_complete ||= scaffold_auto_complete_options[:enable]
464
+ end
465
+
466
+ private
467
+ # scaffold_fields with associations replaced by foreign key fields
468
+ def scaffold_attributes(action = :default)
469
+ instance_variable_set("@scaffold_#{action}_attributes", scaffold_fields(action).collect{|field| scaffold_field_id(field).to_sym})
470
+ end
471
+
472
+ # The conditions to use for the scaffolded autocomplete find.
473
+ def scaffold_auto_complete_conditions(phrase)
474
+ [scaffold_auto_complete_conditions_phrase, (scaffold_auto_complete_search_format_string % phrase.send(scaffold_auto_complete_phrase_modifier))]
475
+ end
476
+
477
+ # The conditions phrase (the sql code with ? place holders) used in the
478
+ # scaffolded autocomplete find.
479
+ def scaffold_auto_complete_conditions_phrase
480
+ scaffold_auto_complete_options[:conditions_phrase] ||= "#{scaffold_auto_complete_name_sql} #{scaffold_auto_complete_search_operator} ?"
481
+ end
482
+
483
+ # Search operator for matching scaffold_auto_complete_name_sql to format_string % phrase,
484
+ # usally 'LIKE', but might be 'ILIKE' on some databases.
485
+ def scaffold_auto_complete_search_operator
486
+ scaffold_auto_complete_options[:search_operator]
487
+ end
488
+
489
+ # SQL fragment (usually column name) that is used when scaffold autocompleting is turned on.
490
+ def scaffold_auto_complete_name_sql
491
+ scaffold_auto_complete_options[:sql_name]
492
+ end
493
+
494
+ # If the auto complete options have been setup, return them. Otherwise,
495
+ # create the auto complete options using the defaults and the existing
496
+ # class instance variable. Can be set as an instance variable.
497
+ def scaffold_auto_complete_options
498
+ return @scaffold_auto_complete_options if @scaffold_auto_complete_options && @scaffold_auto_complete_options[:setup]
499
+ @scaffold_auto_complete_options = @scaffold_auto_complete_options.nil? ? {} : {:enable=>true}.merge(@scaffold_auto_complete_options)
500
+ @scaffold_auto_complete_options = SCAFFOLD_OPTIONS[:auto_complete].merge(@scaffold_auto_complete_options)
501
+ @scaffold_auto_complete_options[:setup] = true
502
+ @scaffold_auto_complete_options
503
+ end
504
+
505
+ # A symbol for a string method to send to the submitted phrase. Usually
506
+ # :downcase to preform a case insensitive search, but may be :to_s for
507
+ # a case sensitive search.
508
+ def scaffold_auto_complete_phrase_modifier
509
+ scaffold_auto_complete_options[:phrase_modifier]
510
+ end
511
+
512
+ # The number of results to return for the scaffolded autocomplete text box.
513
+ def scaffold_auto_complete_results_limit
514
+ scaffold_auto_complete_options[:results_limit]
515
+ end
516
+
517
+ # Format string used with the phrase to choose the type of search. Can be
518
+ # a user defined format string or one of these special symbols:
519
+ # - :substring - Phase matches any substring of scaffold_auto_complete_name_sql
520
+ # - :starting - Phrase matches the start of scaffold_auto_complete_name_sql
521
+ # - :ending - Phrase matches the end of scaffold_auto_complete_name_sql
522
+ # - :exact - Phrase matches scaffold_auto_complete_name_sql exactly
523
+ def scaffold_auto_complete_search_format_string
524
+ {:substring=>'%%%s%%', :starting=>'%s%%', :ending=>'%%%s', :exact=>'%s'}[scaffold_auto_complete_options[:format_string]] || scaffold_auto_complete_options[:format_string]
525
+ end
526
+
527
+ # Condition to ensure field equals value
528
+ def scaffold_equal_condition(field, value)
529
+ ["#{scaffold_table_name}.#{field} = ?", "%#{value}%"]
530
+ end
531
+
532
+ # Filters the provided attributes to just the ones given by scaffold_attributes for
533
+ # the given action.
534
+ def scaffold_filter_attributes(action, attributes)
535
+ allowed_attributes = scaffold_attributes(action).collect{|x| x.to_s}
536
+ attributes.reject{|k,v| !allowed_attributes.include?(k.to_s.split('(')[0])}
537
+ end
538
+
539
+ # Condition to ensure field is not NULL
540
+ def scaffold_notnull_condition(field)
541
+ ["#{scaffold_table_name}.#{field} IS NOT NULL"]
542
+ end
543
+
544
+ # Condition to ensure field is NULL
545
+ def scaffold_null_condition(field)
546
+ ["#{scaffold_table_name}.#{field} IS NULL"]
547
+ end
548
+
549
+ # If search pagination is enabled (by default it is if
550
+ # scaffold_search_results_limit is not nil)
551
+ def scaffold_search_pagination_enabled?
552
+ !scaffold_search_results_limit.nil?
553
+ end
554
+
555
+ # The SQL ORDER BY fragment string when ordering the association.
556
+ # Defaults to the scaffold_select_order for the associated class.
557
+ def scaffold_select_order_association(association)
558
+ scaffold_associated_class(association).scaffold_select_order(:association)
559
+ end
560
+
561
+ # Condition to ensure value is a substring of field
562
+ def scaffold_substring_condition(field, value)
563
+ ["#{scaffold_table_name}.#{field} #{scaffold_auto_complete_search_operator} ?", "%#{value}%"]
564
+ end
565
+
566
+ # Condition to ensure that objects from the associated class are not currently associated with object
567
+ def scaffold_unassociated_condition(association, object)
568
+ klass, left_key, right_key, join_table = scaffold_habtm_reflection_options(association)
569
+ ["#{klass.scaffold_table_name}.#{klass.scaffold_primary_key} NOT IN (SELECT #{join_table}.#{right_key} FROM #{join_table} WHERE #{join_table}.#{left_key} = ?)", object.scaffold_id]
570
+ end
571
+ end