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.
- data/LICENSE +19 -0
- data/README +144 -0
- data/contrib/scaffold_associations_tree/README +9 -0
- data/contrib/scaffold_associations_tree/bullet.gif +0 -0
- data/contrib/scaffold_associations_tree/minus.gif +0 -0
- data/contrib/scaffold_associations_tree/plus.gif +0 -0
- data/contrib/scaffold_associations_tree/scaffold_associations_tree.css +20 -0
- data/contrib/scaffold_associations_tree/scaffold_associations_tree.js +57 -0
- data/contrib/scaffold_auto_complete_style/README +8 -0
- data/contrib/scaffold_auto_complete_style/auto_complete.css +23 -0
- data/contrib/scaffold_form_focus/README +12 -0
- data/contrib/scaffold_form_focus/scaffold_form_focus.js +21 -0
- data/contrib/scaffold_jquery_autocomplete/README +8 -0
- data/contrib/scaffold_jquery_autocomplete/jquery.ui.se_autocomplete.css +22 -0
- data/contrib/scaffold_jquery_autocomplete/jquery.ui.se_autocomplete.js +121 -0
- data/doc/advanced.txt +154 -0
- data/doc/camping.txt +25 -0
- data/doc/controller_spec.txt +20 -0
- data/doc/conversion.txt +102 -0
- data/doc/model_spec.txt +54 -0
- data/doc/ramaze.txt +20 -0
- data/doc/sinatra.txt +20 -0
- data/doc/testing.txt +12 -0
- data/lib/scaffolding_extensions.rb +89 -0
- data/lib/scaffolding_extensions/controller.rb +79 -0
- data/lib/scaffolding_extensions/controller/action_controller.rb +116 -0
- data/lib/scaffolding_extensions/controller/camping.rb +150 -0
- data/lib/scaffolding_extensions/controller/ramaze.rb +116 -0
- data/lib/scaffolding_extensions/controller/sinatra.rb +183 -0
- data/lib/scaffolding_extensions/helper.rb +304 -0
- data/lib/scaffolding_extensions/jquery_helper.rb +58 -0
- data/lib/scaffolding_extensions/meta_controller.rb +337 -0
- data/lib/scaffolding_extensions/meta_model.rb +571 -0
- data/lib/scaffolding_extensions/model.rb +30 -0
- data/lib/scaffolding_extensions/model/active_record.rb +184 -0
- data/lib/scaffolding_extensions/model/ardm.rb +47 -0
- data/lib/scaffolding_extensions/model/data_mapper.rb +190 -0
- data/lib/scaffolding_extensions/overridable.rb +67 -0
- data/lib/scaffolding_extensions/prototype_helper.rb +59 -0
- data/scaffolds/edit.rhtml +12 -0
- data/scaffolds/habtm.rhtml +24 -0
- data/scaffolds/index.rhtml +6 -0
- data/scaffolds/layout.rhtml +82 -0
- data/scaffolds/list.rhtml +13 -0
- data/scaffolds/listtable.rhtml +46 -0
- data/scaffolds/manage.rhtml +15 -0
- data/scaffolds/merge.rhtml +23 -0
- data/scaffolds/new.rhtml +5 -0
- data/scaffolds/search.rhtml +11 -0
- data/scaffolds/show.rhtml +19 -0
- data/test/scaffolding_extensions_test.rb +44 -0
- 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
|