PageRenderTime 53ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/plugins/scaffolding_extensions/lib/scaffolding_extensions.rb

https://bitbucket.org/kapilnakhwa/demo-teachme
Ruby | 854 lines | 568 code | 79 blank | 207 comment | 82 complexity | a1200015d7092cb4acbab095ecdb79b1 MD5 | raw file
  1. # Scaffolding Extensions
  2. module ActiveRecord # :nodoc:
  3. # Modifying class variables allows you to set various defaults for scaffolding.
  4. # Note that if multiple subclasses each modify the class variables, chaos will ensue.
  5. # Class variables have cattr_accessor so that you can set them in environment.rb, such as:
  6. # ActiveRecord::Base.scaffold_convert_text_to_string = true
  7. #
  8. # Available class variables:
  9. # - scaffold_convert_text_to_string: If true, by default, use input type text instead of textarea
  10. # for fields of type text (default: false)
  11. # - scaffold_table_classes: Set the default table classes for different scaffolded HTML tables
  12. # (default: {:form=>'formtable', :list=>'sortable', :show=>'sortable'})
  13. # - scaffold_column_types: Override the default column type for a given attribute
  14. # (default: {'password'=>:password})
  15. # - scaffold_column_options_hash: Override the default column options for a given attribute (default: {})
  16. # - scaffold_association_list_class: Override the html class for the association list in the edit view
  17. # (default: '')
  18. # - scaffold_browse_default_records_per_page - The default number of records per page to show in the
  19. # browse scaffold (default: 10)
  20. # - scaffold_search_results_default_limit - The default limit on scaffolded search results
  21. # (default: nil # no limit)
  22. # - scaffold_auto_complete_default_options: Hash containing the default options to use for the scaffold
  23. # autocompleter (default: {:enable=>false, :sql_name=>'LOWER(name)', :text_field_options=>{:size=>50},
  24. # :format_string=>:substring, :search_operator=>'LIKE', :results_limit=>10, :phrase_modifier=>:downcase,
  25. # :skip_style=>false})
  26. #
  27. # Modifying instance variables in each class affects scaffolding for that class only (always defaults to nil).
  28. # Available instance variables:
  29. #
  30. # - scaffold_fields: List of field names to include in the scaffolded forms.
  31. # Values in the list should be either actual fields names, or names of belongs_to
  32. # associations (in which case select boxes will be used in the scaffolded forms).
  33. # Uses content_columns + belongs_to associations if not specified.
  34. # (example: %w'name number rating')
  35. # - scaffold_select_order: SQL fragment string setting the order in which scaffolded records are shown
  36. # (example: 'firstname, lastname')
  37. # - scaffold_include: Any classes that should include by default when displaying the
  38. # scaffold name. Eager loading is used so that N+1 queries aren't used for displaying N
  39. # records, assuming that associated records used in scaffold_name are included in this.
  40. # (example: [:artist, :album])
  41. # - scaffold_associations: List of associations to display on the scaffolded edit page for the object.
  42. # Uses all associations if not specified (example: %w'artist albums')
  43. # - scaffold_associations_path: String path to the template to use to render the associations
  44. # for the edit page. Uses the controller's scaffold path if not specified.
  45. # (example: "#{RAILS_ROOT}/lib/model_associations.rhtml")
  46. # - scaffold_browse_records_per_page - The number of records per page to show in the
  47. # browse scaffold. Uses scaffold_browse_default_records_per_page if not specified (example: 25)
  48. # - scaffold_search_results_limit - The limit on the number of records in the scaffolded search
  49. # results page (example: 25)
  50. # - scaffold_auto_complete_options - Hash merged with the auto complete default options to set
  51. # the auto complete options for the model. If the auto complete default options are set
  52. # with :enable=>false, setting this variable turns on autocompleting. If the auto complete
  53. # default options are set with :enable=>true, autocompleting can be turned off for this
  54. # model with {:enable=>false}. (example: {})
  55. #
  56. # scaffold_table_classes, scaffold_column_types, and scaffold_column_options_hash can also
  57. # be specified as instance variables, in which case they will override the class variable
  58. # defaults.
  59. class Base
  60. @@scaffold_convert_text_to_string = false
  61. @@scaffold_table_classes = {:form=>'formtable', :list=>'sortable', :show=>'sortable'}
  62. @@scaffold_column_types = {'password'=>:password}
  63. @@scaffold_column_options_hash = {}
  64. @@scaffold_association_list_class = ''
  65. @@scaffold_browse_default_records_per_page = 10
  66. @@scaffold_search_results_default_limit = nil
  67. @@scaffold_auto_complete_default_options = {:enable=>false, :sql_name=>'LOWER(name)',
  68. :text_field_options=>{:size=>50}, :format_string=>:substring, :search_operator=>'LIKE',
  69. :results_limit=>10, :phrase_modifier=>:downcase, :skip_style=>false}
  70. cattr_accessor :scaffold_convert_text_to_string, :scaffold_table_classes, :scaffold_column_types, :scaffold_column_options_hash, :scaffold_association_list_class, :scaffold_auto_complete_default_options, :scaffold_browse_default_records_per_page, :scaffold_search_results_default_limit
  71. class << self
  72. attr_accessor :scaffold_select_order, :scaffold_include, :scaffold_associations_path
  73. # Checks all files in the models directory to return strings for all models
  74. # that are a subclass of the current class
  75. def all_models
  76. Dir["#{RAILS_ROOT}/app/models/*.rb"].collect{|file|File.basename(file).sub(/\.rb$/, '')}.sort.reject{|model| (! model.camelize.constantize.ancestors.include?(self)) rescue true}
  77. end
  78. # Merges the record with id from into the record with id to. Updates all
  79. # associated records for the record with id from to be assocatiated with
  80. # the record with id to instead, and then deletes the record with id from.
  81. #
  82. # Returns false if the ids given are the same.
  83. def merge_records(from, to)
  84. return false if from == to
  85. transaction do
  86. reflect_on_all_associations.each{|reflection| reflection_merge(reflection, from, to)}
  87. destroy(from)
  88. end
  89. true
  90. end
  91. def interpolate_conditions(conditions)
  92. return conditions unless conditions
  93. aliased_table_name = table_name
  94. instance_eval("%@#{conditions.gsub('@', '\@')}@")
  95. end
  96. # Updates associated records for a given reflection and from record to point to the
  97. # to record
  98. def reflection_merge(reflection, from, to)
  99. foreign_key = reflection.options[:foreign_key] || table_name.classify.foreign_key
  100. sql = case reflection.macro
  101. when :has_one, :has_many
  102. "UPDATE #{reflection.klass.table_name} SET #{foreign_key} = #{to} WHERE #{foreign_key} = #{from}\n"
  103. when :has_and_belongs_to_many
  104. join_table = reflection.options[:join_table] || ( table_name < reflection.klass.table_name ? '#{table_name}_#{reflection.klass.table_name}' : '#{reflection.klass.table_name}_#{table_name}')
  105. "UPDATE #{join_table} SET #{foreign_key} = #{to} WHERE #{foreign_key} = #{from}\n"
  106. else return
  107. end
  108. connection.update(sql)
  109. end
  110. # List of strings for associations to display on the scaffolded edit page
  111. def scaffold_associations
  112. @scaffold_associations ||= reflect_on_all_associations.collect{|r|r.name.to_s}.sort
  113. end
  114. # Returns the list of fields to display on the scaffolded forms. Defaults
  115. # to displaying all usually scaffolded columns with the addition of belongs
  116. # to associations.
  117. def scaffold_fields
  118. return @scaffold_fields if @scaffold_fields
  119. @scaffold_fields = columns.reject{|c| c.primary || c.name =~ /_count$/ || c.name == inheritance_column }.collect{|c| c.name}
  120. reflect_on_all_associations.each do |reflection|
  121. next unless reflection.macro == :belongs_to
  122. @scaffold_fields.delete((reflection.options[:foreign_key] || reflection.klass.table_name.classify.foreign_key).to_s)
  123. @scaffold_fields.push(reflection.name.to_s)
  124. end
  125. @scaffold_fields.sort!
  126. @scaffold_fields
  127. end
  128. # Returns the scaffolded table class for a given scaffold type.
  129. def scaffold_table_class(type)
  130. @scaffold_table_classes ||= scaffold_table_classes
  131. @scaffold_table_classes[type]
  132. end
  133. # Returns the column type for the given scaffolded column name. First checks to see
  134. # if a value has been overriden using a class or instance variable, otherwise uses
  135. # the default column type.
  136. def scaffold_column_type(column_name)
  137. @scaffold_column_types ||= scaffold_column_types
  138. if @scaffold_column_types[column_name]
  139. @scaffold_column_types[column_name]
  140. elsif columns_hash.include?(column_name)
  141. type = columns_hash[column_name].type
  142. (scaffold_convert_text_to_string and type == :text) ? :string : type
  143. end
  144. end
  145. # Returns any special options for a given attribute
  146. def scaffold_column_options(column_name)
  147. @scaffold_column_options_hash ||= scaffold_column_options_hash
  148. @scaffold_column_options_hash[column_name]
  149. end
  150. # The number of records to show on each page when using the browse scaffold
  151. def scaffold_browse_records_per_page
  152. @scaffold_browse_records_per_page ||= scaffold_browse_default_records_per_page
  153. end
  154. # The maximum number of results to show on the scaffolded search results page
  155. def scaffold_search_results_limit
  156. @scaffold_search_results_limit ||= scaffold_search_results_default_limit
  157. end
  158. # If the auto complete options have been setup, return them. Otherwise,
  159. # create the auto complete options using the defaults and the existing
  160. # class instance variable.
  161. def scaffold_auto_complete_options
  162. return @scaffold_auto_complete_options if @scaffold_auto_complete_options && @scaffold_auto_complete_options[:setup]
  163. @scaffold_auto_complete_options = @scaffold_auto_complete_options.nil? ? {} : {:enable=>true}.merge(@scaffold_auto_complete_options)
  164. @scaffold_auto_complete_options = scaffold_auto_complete_default_options.merge(@scaffold_auto_complete_options)
  165. @scaffold_auto_complete_options[:setup] = true
  166. @scaffold_auto_complete_options
  167. end
  168. # Whether this class should use an autocompleting text box instead of a select
  169. # box for choosing items.
  170. def scaffold_use_auto_complete
  171. scaffold_auto_complete_options[:enable]
  172. end
  173. # SQL fragment (usually column name) that is used when scaffold autocompleting is turned on.
  174. def scaffold_name_sql
  175. scaffold_auto_complete_options[:sql_name]
  176. end
  177. # Options for the scaffold autocompleting text field
  178. def scaffold_auto_complete_text_field_options
  179. scaffold_auto_complete_options[:text_field_options]
  180. end
  181. def scaffold_auto_complete_skip_style
  182. scaffold_auto_complete_options[:skip_style]
  183. end
  184. # Format string used with the phrase to choose the type of search. Can be
  185. # a user defined format string or one of these special symbols:
  186. # - :substring - Phase matches any substring of scaffold_name_sql
  187. # - :starting - Phrase matches the start of scaffold_name_sql
  188. # - :ending - Phrase matches the end of scaffold_name_sql
  189. # - :exact - Phrase matches scaffold_name_sql exactly
  190. def scaffold_auto_complete_search_format_string
  191. {:substring=>'%%%s%%', :starting=>'%s%%', :ending=>'%%%s', :exact=>'%s'}[scaffold_auto_complete_options[:format_string]] || scaffold_auto_complete_options[:format_string]
  192. end
  193. # Search operator for matching scaffold_name_sql to format_string % phrase,
  194. # usally 'LIKE', but might be 'ILIKE' on some databases.
  195. def scaffold_auto_complete_search_operator
  196. scaffold_auto_complete_options[:search_operator]
  197. end
  198. # The number of results to return for the scaffolded autocomplete text box.
  199. def scaffold_auto_complete_results_limit
  200. scaffold_auto_complete_options[:results_limit]
  201. end
  202. # The conditions phrase (the sql code with ? place holders) used in the
  203. # scaffolded autocomplete find.
  204. def scaffold_auto_complete_conditions_phrase
  205. scaffold_auto_complete_options[:conditions_phrase] ||= "#{scaffold_name_sql} #{scaffold_auto_complete_search_operator} ?"
  206. end
  207. # A symbol for a string method to send to the submitted phrase. Usually
  208. # :downcase to preform a case insensitive search, but may be :to_s for
  209. # a case sensitive search.
  210. def scaffold_auto_complete_phrase_modifier
  211. scaffold_auto_complete_options[:phrase_modifier]
  212. end
  213. # The conditions to use for the scaffolded autocomplete find.
  214. def scaffold_auto_complete_conditions(phrase)
  215. [scaffold_auto_complete_conditions_phrase, (scaffold_auto_complete_search_format_string % phrase.send(scaffold_auto_complete_phrase_modifier))]
  216. end
  217. # Return all records that match the given phrase (usually a substring of
  218. # the most important column).
  219. def scaffold_auto_complete_find(phrase, options = {})
  220. find_options = { :limit => scaffold_auto_complete_results_limit,
  221. :conditions => scaffold_auto_complete_conditions(phrase),
  222. :order => scaffold_select_order,
  223. :include => scaffold_include}.merge(options)
  224. find(:all, find_options)
  225. end
  226. end
  227. # Merges the current record into the record given and returns the record given.
  228. # Returns false if the record isn't the same class as the current class or
  229. # if you try to merge a record into itself (which would be the same as deleting it).
  230. def merge_into(record)
  231. return false unless record.class == self.class && self.class.merge_records(self, id, record.id)
  232. record.reload
  233. end
  234. # The name given to the item that is used in various places in the scaffold. For example,
  235. # it is used whenever the record is displayed in a select box. Should be unique for each record.
  236. # Should be overridden by subclasses unless they have a unique attribute named 'name'.
  237. def scaffold_name
  238. self[:name] or id
  239. end
  240. # scaffold_name prefixed with id, used for scaffold autocompleting (a nice hack thanks
  241. # to String#to_i)
  242. def scaffold_name_with_id
  243. "#{id} - #{scaffold_name}"
  244. end
  245. end
  246. end
  247. module ActionView # :nodoc:
  248. module Helpers # :nodoc:
  249. # Changes the default scaffolding of new/edit forms to handle associated
  250. # records, and uses a table to display the form.
  251. module ActiveRecordHelper
  252. # Uses a table to display the form widgets, so that everything lines up
  253. # nicely. Handles associated records. Also allows for a different set
  254. # of fields to be specified instead of the default scaffold_fields.
  255. def all_input_tags(record, record_name, options)
  256. input_block = options[:input_block] || default_input_block
  257. rows = (options[:fields] || record.class.scaffold_fields).collect do |field|
  258. reflection = record.class.reflect_on_association(field.to_sym)
  259. if reflection
  260. input_block.call(record_name, reflection)
  261. else
  262. input_block.call(record_name, record.column_for_attribute(field))
  263. end
  264. end
  265. "\n<table class='#{record.class.scaffold_table_class :form}'><tbody>\n#{rows.join}</tbody></table><br />"
  266. end
  267. # Wraps each widget and widget label in a table row
  268. def default_input_block
  269. Proc.new do |record, column|
  270. if column.class.name =~ /Reflection/
  271. if column.macro == :belongs_to
  272. "<tr><td>#{column.name.to_s.humanize}:</td><td>#{association_select_tag(record, column.name)}</td></tr>\n"
  273. end
  274. else
  275. "<tr><td>#{column.human_name}:</td><td>#{input(record, column.name)}</td></tr>\n"
  276. end
  277. end
  278. end
  279. # Returns a select box displaying the possible records that can be associated.
  280. # If scaffold autocompleting is turned on for the associated model, uses
  281. # an autocompleting text box. Otherwise, creates a select box using
  282. # the associated model's scaffold_name and scaffold_select_order.
  283. def association_select_tag(record, association)
  284. reflection = record.camelize.constantize.reflect_on_association(association)
  285. foreign_key = reflection.options[:foreign_key] || reflection.klass.table_name.classify.foreign_key
  286. if reflection.klass.scaffold_use_auto_complete
  287. scaffold_text_field_with_auto_complete(record, foreign_key, reflection.klass.name.underscore)
  288. else
  289. items = reflection.klass.find(:all, :order => reflection.klass.scaffold_select_order, :conditions => reflection.klass.interpolate_conditions(reflection.options[:conditions]), :include=>reflection.klass.scaffold_include)
  290. items.sort! {|x,y| x.scaffold_name <=> y.scaffold_name} if reflection.klass.scaffold_include
  291. select(record, foreign_key, items.collect{|i| [i.scaffold_name, i.id]}, {:include_blank=>true})
  292. end
  293. end
  294. end
  295. # Methods used to implement scaffold autocompleting
  296. module JavaScriptMacrosHelper
  297. # Text field with autocompleting used for belongs_to associations of main object in scaffolded forms.
  298. def scaffold_text_field_with_auto_complete(object, method, associated_class, tag_options = {})
  299. klass = associated_class.to_s.camelize.constantize
  300. foreign_key = instance_variable_get("@#{object}").send(method)
  301. ((klass.scaffold_auto_complete_skip_style ? '' : auto_complete_stylesheet) +
  302. text_field(object, method, klass.scaffold_auto_complete_text_field_options.merge({:value=>(foreign_key ? klass.find(foreign_key).scaffold_name_with_id : '')}).merge(tag_options)) +
  303. content_tag("div", "", :id => "#{object}_#{method}_scaffold_auto_complete", :class => "auto_complete") +
  304. scaffold_auto_complete_field("#{object}_#{method}", { :url => { :action => "scaffold_auto_complete_for_#{associated_class}" } }))
  305. end
  306. # Text field with autocompleting for classes without an attached object.
  307. def scaffold_text_field_tag_with_auto_complete(id, klass, tag_options = {})
  308. ((klass.scaffold_auto_complete_skip_style ? '' : auto_complete_stylesheet) +
  309. text_field_tag(id, nil, klass.scaffold_auto_complete_text_field_options.merge(tag_options)) +
  310. content_tag("div", "", :id => "#{id}_scaffold_auto_complete", :class => "auto_complete") +
  311. scaffold_auto_complete_field(id, { :url => { :action => "scaffold_auto_complete_for_#{klass.name.underscore}"} }))
  312. end
  313. # Javascript code for setting up autocomplete for given field_id
  314. def scaffold_auto_complete_field(field_id, options = {})
  315. javascript_tag("var #{field_id}_auto_completer = new Ajax.Autocompleter('#{field_id}', '#{options[:update] || "#{field_id}_scaffold_auto_complete"}', '#{url_for(options[:url])}', {paramName:'id'})")
  316. end
  317. # Formats auto_complete_result using model's scaffold_name_with_id
  318. def scaffold_auto_complete_result(entries)
  319. return unless entries
  320. content_tag("ul", entries.map{|entry| content_tag("li", h(entry.scaffold_name_with_id))}.uniq)
  321. end
  322. end
  323. class InstanceTag
  324. # Gets the default options for the attribute and merges them with the given options.
  325. # Chooses an appropriate widget based on attribute's column type.
  326. def to_tag(options = {})
  327. options = (object.class.scaffold_column_options(@method_name) || {}).merge(options)
  328. case column_type
  329. when :string, :integer, :float
  330. to_input_field_tag("text", options)
  331. when :password
  332. to_input_field_tag("password", options)
  333. when :text
  334. to_text_area_tag(options)
  335. when :date
  336. to_date_select_tag(options)
  337. when :datetime
  338. to_datetime_select_tag(options)
  339. when :boolean
  340. to_boolean_select_tag(options)
  341. end
  342. end
  343. # Returns three valued select widget, for null, false, and true, with the appropriate
  344. # value selected
  345. def to_boolean_select_tag(options = {})
  346. options = options.stringify_keys
  347. add_default_name_and_id(options)
  348. "<select#{tag_options(options)}><option value=''#{selected(value.nil?)}>&nbsp;</option><option value='f'#{selected(value == false)}>False</option><option value='t'#{selected(value)}>True</option></select>"
  349. end
  350. # Returns XHTML compliant fragment for whether the value is selected or not
  351. def selected(value)
  352. value ? " selected='selected'" : ''
  353. end
  354. # Allow overriding of the column type by asking the ActiveRecord for the appropriate column type.
  355. def column_type
  356. object.class.scaffold_column_type(@method_name)
  357. end
  358. end
  359. end
  360. end
  361. # Contains methods used by the scaffolded forms.
  362. module ScaffoldHelper
  363. # Returns link if the controller will respond to the given action, otherwise returns the text itself.
  364. # If :action is not specified in the options, returns link.
  365. def link_to_or_text(name, options={}, html_options=nil, *parameters_for_method_reference)
  366. link_to_or_plain_text(name, name, options, html_options, *parameters_for_method_reference)
  367. end
  368. # Returns link if the controller will respond to the given action, otherwise returns "".
  369. # If :action is not specified in the options, returns link.
  370. def link_to_or_blank(name, options={}, html_options=nil, *parameters_for_method_reference)
  371. link_to_or_plain_text(name, '', options, html_options, *parameters_for_method_reference)
  372. end
  373. # Returns link if the controller will respond to the given action, otherwise returns plain.
  374. # If :action is not specified in the options, returns link.
  375. def link_to_or_plain_text(name, plain, options={}, html_options=nil, *parameters_for_method_reference)
  376. _controller = options[:controller] ? options[:controller].camelize.constantize : controller
  377. if options[:action]
  378. _controller.respond_to?(options[:action]) ? link_to(name, options, html_options, *parameters_for_method_reference) : plain
  379. else link_to(name, options, html_options, *parameters_for_method_reference)
  380. end
  381. end
  382. # Returns html fragment containing information on related models and objects.
  383. # The fragment will include links to scaffolded pages for the related items if the links would work.
  384. def association_links
  385. filename = (@scaffold_class.scaffold_associations_path || controller.scaffold_path("associations"))
  386. controller.send(:render_to_string, {:file=>filename, :layout=>false}) if File.file?(filename)
  387. end
  388. # Returns link to the scaffolded management page for the model if it was created by the scaffolding.
  389. def manage_link
  390. "<br />#{link_to("Manage #{@scaffold_plural_name.humanize.downcase}", :action => "manage#{@scaffold_suffix}")}" if @scaffold_methods.include?(:manage)
  391. end
  392. # Returns an appropriate scaffolded data entry form for the model, with any related error messages.
  393. def scaffold_form(action, options = {})
  394. "#{error_messages_for(@scaffold_singular_name)}\n#{form(@scaffold_singular_name, {:action=>"#{action}#{@scaffold_suffix}", :submit_value=>"#{action.capitalize} #{@scaffold_singular_name.humanize.downcase}"}.merge(options))}"
  395. end
  396. # Returns associated object's scaffold_name if column is an association, otherwise returns column value.
  397. def scaffold_value(entry, column)
  398. entry.send(column).methods.include?('scaffold_name') ? entry.send(column).scaffold_name : entry.send(column)
  399. end
  400. end
  401. module ActionController # :nodoc:
  402. # Two variables can be set that affect scaffolding, either as class variables
  403. # (which specifies the default for all classes) or instance variables (which
  404. # specifies the values for that class only).
  405. #
  406. # - scaffold_template_dir: the location of the scaffold templates (default:
  407. # "#{File.dirname(__FILE__)}/../scaffolds" # the plugin's default scaffold directory)
  408. # - default_scaffold_methods: the default methods added by the scaffold function
  409. # (default: [:manage, :show, :destroy, :edit, :new, :search, :merge, :browse] # all methods)
  410. class Base
  411. @@scaffold_template_dir = "#{File.dirname(__FILE__)}/../scaffolds"
  412. @@default_scaffold_methods = [:manage, :show, :destroy, :edit, :new, :search, :merge, :browse]
  413. cattr_accessor = :scaffold_template_dir, :default_scaffold_methods
  414. class << self
  415. # The location of the scaffold templates
  416. def scaffold_template_dir
  417. @scaffold_template_dir ||= @@scaffold_template_dir
  418. end
  419. # The methods that should be added by the scaffolding function by default
  420. def default_scaffold_methods
  421. @default_scaffold_methods ||= @@default_scaffold_methods
  422. end
  423. # Returns path to the given scaffold rhtml file
  424. def scaffold_path(template_name)
  425. File.join(scaffold_template_dir, template_name+'.rhtml')
  426. end
  427. # Normalizes scaffold options, allowing submission of symbols or arrays
  428. def normalize_scaffold_options(options)
  429. case options
  430. when Array then options
  431. when Symbol then [options]
  432. else []
  433. end
  434. end
  435. # Create controller instance method for returning results to the scaffold autocompletor
  436. # for the given model.
  437. def scaffold_auto_complete_for(object, options = {})
  438. define_method("scaffold_auto_complete_for_#{object}") do
  439. @items = object.to_s.camelize.constantize.scaffold_auto_complete_find(params[:id], options)
  440. render :inline => "<%= scaffold_auto_complete_result(@items) %>"
  441. end
  442. end
  443. # Setup scaffold auto complete for them model if it is requested by model
  444. # and it hasn't already been setup.
  445. def setup_scaffold_auto_complete_for(model_id)
  446. scaffold_auto_complete_for(model_id) if model_id.to_s.camelize.constantize.scaffold_use_auto_complete && !respond_to?("scaffold_auto_complete_for_#{model_id}")
  447. end
  448. # Setup scaffold_auto_complete_for for the given controller for all models
  449. # implementing scaffold autocompleting. If scaffolding is used in multiple
  450. # controllers, scaffold_auto_complete_for methods for all models will be added
  451. # to all controllers.
  452. def setup_scaffold_auto_completes
  453. return if @scaffold_auto_completes_are_setup
  454. ActiveRecord::Base.all_models.each{|model| setup_scaffold_auto_complete_for(model.to_sym)}
  455. @scaffold_auto_completes_are_setup = true
  456. end
  457. end
  458. # Returns path to the given scaffold rhtml file
  459. def scaffold_path(template_name)
  460. self.class.scaffold_path(template_name)
  461. end
  462. private
  463. # Renders manually created page if it exists, otherwise renders a scaffold form.
  464. # If a layout is specified (either in the controller or as an option), use that layout,
  465. # otherwise uses the scaffolded layout.
  466. def render_scaffold_template(action, options = {}) # :doc:
  467. options = if template_exists?("#{self.class.controller_path}/#{action}")
  468. {:action=>action}.merge(options)
  469. else
  470. if active_layout || options.include?(:layout)
  471. {:file=>scaffold_path(action.split('_')[0]), :layout=>active_layout}.merge(options)
  472. else
  473. @content_for_layout = render_to_string({:file=>scaffold_path(action.split('_')[0])}.merge(options))
  474. {:file=>scaffold_path("layout")}
  475. end
  476. end
  477. render(options)
  478. end
  479. # Converts all items in the array to integers and discards non-zero values
  480. def multiple_select_ids(arr) # :doc:
  481. arr = [arr] unless arr.is_a?(Array)
  482. arr.collect{|x| x.to_i}.delete_if{|x| x == 0}
  483. end
  484. # Adds conditions for the scaffolded search query. Uses LIKE OR ILIKE for string attributes,
  485. # IS TRUE|FALSE for boolean attributes, and = for other attributes.
  486. def scaffold_search_add_condition(conditions, record, field) # :doc:
  487. column = record.column_for_attribute(field)
  488. if column and column.klass == String
  489. if record.send(field).length > 0
  490. conditions[0] << "#{record.class.table_name}.#{field} #{record.class.scaffold_auto_complete_search_operator} ?"
  491. conditions << "%#{record.send(field)}%"
  492. end
  493. elsif column.klass == Object
  494. conditions[0] << "#{record.class.table_name}.#{field} IS #{record.send(field) ? 'TRUE' : 'FALSE'}"
  495. else
  496. conditions[0] << "#{record.class.table_name}.#{field} = ?"
  497. conditions << record.send(field)
  498. end
  499. end
  500. end
  501. module Scaffolding # :nodoc:
  502. module ClassMethods
  503. # Expands on the default Rails scaffold function.
  504. # Takes the following additional options:
  505. #
  506. # - :except: symbol or array of method symbols not to add
  507. # - :only: symbol or array of method symbols to use instead of the default
  508. # - :habtm: symbol or array of symbols of habtm associated classes,
  509. # habtm scaffolds will be created for each one
  510. # - :setup_auto_completes: if set to false, don't create scaffold auto
  511. # complete actions for all models
  512. #
  513. # The following method symbols are used to control the methods that get
  514. # added by the scaffold function:
  515. #
  516. # - :manage: Page that has links to all the other methods. Also used
  517. # as the index page unless :suffix=>true
  518. # - :show: Shows a select box with all objects, allowing the user to chose
  519. # one, which then shows the attribute name and value for scaffolded fields
  520. # - :destroy: Shows a select box with all objects, allowing the user to chose
  521. # one to delete
  522. # - :edit: Shows a select box with all objects, allowing the user to chose
  523. # one to edit. Also shows associations specified in the model's
  524. # scaffold_associations, allowing you easy access to manage associated models,
  525. # add new objects for has_many associations, and edit the has_and_belongs_to_many
  526. # associations.
  527. # - :new: Form for creating new objects
  528. # - :search: Simple search form using the same attributes as the new/edit
  529. # form. The results page has links to show, edit, or destroy the object
  530. # - :merge: Brings up two select boxes each populated with all objects,
  531. # allowing the user to pick one to merge into the other
  532. # - :browse: Browse all model objects, similar to the default Rails list scaffold
  533. def scaffold(model_id, options = {})
  534. options.assert_valid_keys(:class_name, :suffix, :except, :only, :habtm, :setup_auto_completes)
  535. singular_name = model_id.to_s.underscore.singularize
  536. class_name = options[:class_name] || singular_name.camelize
  537. plural_name = singular_name.pluralize
  538. suffix = options[:suffix] ? "_#{singular_name}" : ""
  539. add_methods = options[:only] ? normalize_scaffold_options(options[:only]) : self.default_scaffold_methods
  540. add_methods -= normalize_scaffold_options(options[:except]) if options[:except]
  541. normalize_scaffold_options(options[:habtm]).each{|habtm_class| scaffold_habtm(model_id, habtm_class, false)}
  542. setup_scaffold_auto_completes unless options[:setup_auto_completes] == false
  543. if add_methods.include?(:manage)
  544. module_eval <<-"end_eval", __FILE__, __LINE__
  545. def manage#{suffix}
  546. render#{suffix}_scaffold "manage#{suffix}"
  547. end
  548. end_eval
  549. unless options[:suffix]
  550. module_eval <<-"end_eval", __FILE__, __LINE__
  551. def index
  552. manage
  553. end
  554. end_eval
  555. end
  556. end
  557. if add_methods.include?(:show) or add_methods.include?(:destroy) or add_methods.include?(:edit)
  558. module_eval <<-"end_eval", __FILE__, __LINE__
  559. def list#{suffix}
  560. @scaffold_action ||= 'edit'
  561. unless #{class_name}.scaffold_use_auto_complete
  562. @#{plural_name} = #{class_name}.find(:all, :order=>#{class_name}.scaffold_select_order, :include=>#{class_name}.scaffold_include)
  563. @#{plural_name}.sort! {|x,y| x.scaffold_name <=> y.scaffold_name} if #{class_name}.scaffold_include
  564. end
  565. render#{suffix}_scaffold "list#{suffix}"
  566. end
  567. end_eval
  568. end
  569. if add_methods.include?(:show)
  570. module_eval <<-"end_eval", __FILE__, __LINE__
  571. def show#{suffix}
  572. if params[:id]
  573. @#{singular_name} = #{class_name}.find(params[:id].to_i, :include=>#{class_name}.scaffold_include)
  574. @scaffold_associations_readonly = true
  575. render#{suffix}_scaffold
  576. else
  577. @scaffold_action = 'show'
  578. list#{suffix}
  579. end
  580. end
  581. end_eval
  582. end
  583. if add_methods.include?(:destroy)
  584. module_eval <<-"end_eval", __FILE__, __LINE__
  585. def destroy#{suffix}
  586. if params[:id]
  587. #{class_name}.find(params[:id].to_i).destroy
  588. flash[:notice] = "#{singular_name.humanize} was successfully destroyed"
  589. redirect_to :action => "destroy#{suffix}"
  590. else
  591. @scaffold_action = 'destroy'
  592. list#{suffix}
  593. end
  594. end
  595. end_eval
  596. end
  597. if add_methods.include?(:edit)
  598. module_eval <<-"end_eval", __FILE__, __LINE__
  599. def edit#{suffix}
  600. if params[:id]
  601. @#{singular_name} = #{class_name}.find(params[:id].to_i)
  602. render#{suffix}_scaffold
  603. else
  604. @scaffold_action = 'edit'
  605. list#{suffix}
  606. end
  607. end
  608. def update#{suffix}
  609. @#{singular_name} = #{class_name}.find(params[:id])
  610. @#{singular_name}.attributes = params[:#{singular_name}]
  611. if @#{singular_name}.save
  612. flash[:notice] = "#{singular_name.humanize} was successfully updated"
  613. redirect_to :action => "edit#{suffix}"
  614. else
  615. render#{suffix}_scaffold('edit')
  616. end
  617. end
  618. end_eval
  619. end
  620. if add_methods.include?(:new)
  621. module_eval <<-"end_eval", __FILE__, __LINE__
  622. def new#{suffix}
  623. @#{singular_name} = #{class_name}.new(params[:#{singular_name}])
  624. render#{suffix}_scaffold
  625. end
  626. def create#{suffix}
  627. @#{singular_name} = #{class_name}.new(params[:#{singular_name}])
  628. if @#{singular_name}.save
  629. flash[:notice] = "#{singular_name.humanize} was successfully created"
  630. redirect_to :action => "new#{suffix}"
  631. else
  632. render#{suffix}_scaffold('new')
  633. end
  634. end
  635. end_eval
  636. end
  637. if add_methods.include?(:search)
  638. module_eval <<-"end_eval", __FILE__, __LINE__
  639. def search#{suffix}
  640. @#{singular_name} = #{class_name}.new
  641. @scaffold_fields = @#{singular_name}.class.scaffold_fields
  642. @scaffold_nullable_fields = @#{singular_name}.class.scaffold_fields.collect do |field|
  643. reflection = @#{singular_name}.class.reflect_on_association(field.to_sym)
  644. reflection ? (reflection.options[:foreign_key] || reflection.klass.table_name.classify.foreign_key) : field
  645. end
  646. render#{suffix}_scaffold('search#{suffix}')
  647. end
  648. def results#{suffix}
  649. record = #{class_name}.new(params["#{singular_name}"])
  650. conditions = [[]]
  651. includes = []
  652. if params[:#{singular_name}]
  653. #{class_name}.scaffold_fields.each do |field|
  654. reflection = #{class_name}.reflect_on_association(field.to_sym)
  655. if reflection
  656. includes << field.to_sym
  657. field = (reflection.options[:foreign_key] || reflection.klass.table_name.classify.foreign_key).to_s
  658. end
  659. next if (params[:null] and params[:null].include?(field)) or (params[:notnull] and params[:notnull].include?(field))
  660. scaffold_search_add_condition(conditions, record, field) if params[:#{singular_name}][field] and params[:#{singular_name}][field].length > 0
  661. end
  662. end
  663. params[:null].each {|field| conditions[0] << field + ' IS NULL' } if params[:null]
  664. params[:notnull].each {|field| conditions[0] << field + ' IS NOT NULL' } if params[:notnull]
  665. conditions[0] = conditions[0].join(' AND ')
  666. conditions[0] = '1=1' if conditions[0].length == 0
  667. @#{plural_name} = #{class_name}.find(:all, :conditions=>conditions, :include=>includes, :order=>#{class_name}.scaffold_select_order, :limit=>#{class_name}.scaffold_search_results_limit)
  668. render#{suffix}_scaffold('listtable#{suffix}')
  669. end
  670. end_eval
  671. end
  672. if add_methods.include?(:merge)
  673. module_eval <<-"end_eval", __FILE__, __LINE__
  674. def merge#{suffix}
  675. unless #{class_name}.scaffold_use_auto_complete
  676. @#{plural_name} = #{class_name}.find(:all, :order=>#{class_name}.scaffold_select_order, :include=>#{class_name}.scaffold_include)
  677. @#{plural_name}.sort! {|x,y| x.scaffold_name <=> y.scaffold_name} if #{class_name}.scaffold_include
  678. end
  679. render#{suffix}_scaffold('merge#{suffix}')
  680. end
  681. def merge_update#{suffix}
  682. flash[:notice] = if #{class_name}.merge_records(params[:from].to_i, params[:to].to_i)
  683. "#{plural_name.humanize} were successfully merged"
  684. else "Error merging #{plural_name.humanize.downcase}"
  685. end
  686. redirect_to :action=>'merge#{suffix}'
  687. end
  688. end_eval
  689. end
  690. if add_methods.include?(:browse)
  691. module_eval <<-"end_eval", __FILE__, __LINE__
  692. def browse#{suffix}
  693. @#{singular_name}_pages, @#{plural_name} = paginate(:#{plural_name}, :order=>#{class_name}.scaffold_select_order, :include=>#{class_name}.scaffold_fields.collect{|field| field.to_sym if #{class_name}.reflect_on_association(field.to_sym)}.compact, :per_page => #{class_name}.scaffold_browse_records_per_page)
  694. render#{suffix}_scaffold('listtable#{suffix}')
  695. end
  696. end_eval
  697. end
  698. module_eval <<-"end_eval", __FILE__, __LINE__
  699. add_template_helper(ScaffoldHelper)
  700. private
  701. def render#{suffix}_scaffold(action=nil, options={})
  702. action ||= caller_method_name(caller)
  703. @scaffold_class = #{class_name}
  704. @scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
  705. @scaffold_methods = #{add_methods.inspect}
  706. @scaffold_suffix = "#{suffix}"
  707. @scaffold_singular_object = @#{singular_name}
  708. @scaffold_plural_object = @#{plural_name}
  709. add_instance_variables_to_assigns
  710. render_scaffold_template(action, options)
  711. end
  712. def caller_method_name(caller)
  713. caller.first.scan(/`(.*)'/).first.first # ' ruby-mode
  714. end
  715. end_eval
  716. end
  717. # Scaffolds a habtm association for two classes using two select boxes, or
  718. # a select box for removing associations and an autocompleting text box for
  719. # adding associations. By default, scaffolds the association both ways.
  720. def scaffold_habtm(singular, many, both_ways = true)
  721. singular_class, many_class = singular.to_s.singularize.camelize.constantize, many.to_s.singularize.camelize.constantize
  722. singular_name, many_class_name = singular_class.name, many_class.name
  723. many_name = many_class.name.pluralize.underscore
  724. reflection = singular_class.reflect_on_association(many_name.to_sym)
  725. return false if reflection.nil? or reflection.macro != :has_and_belongs_to_many
  726. foreign_key = reflection.options[:foreign_key] || singular_class.table_name.classify.foreign_key
  727. association_foreign_key = reflection.options[:association_foreign_key] || many_class.table_name.classify.foreign_key
  728. join_table = reflection.options[:join_table] || ( singular_name < many_class_name ? '#{singular_name}_#{many_class_name}' : '#{many_class_name}_#{singular_name}')
  729. suffix = "_#{singular_name.underscore}_#{many_name}"
  730. setup_scaffold_auto_complete_for(many_class_name.underscore.to_sym)
  731. module_eval <<-"end_eval", __FILE__, __LINE__
  732. def edit#{suffix}
  733. @singular_name = "#{singular_name}"
  734. @many_name = "#{many_name.gsub('_',' ')}"
  735. @singular_object = #{singular_name}.find(params[:id])
  736. @many_class = #{many_class_name}
  737. @items_to_remove = #{many_class_name}.find(:all, :conditions=>["#{many_class.primary_key} IN (SELECT #{association_foreign_key} FROM #{join_table} WHERE #{join_table}.#{foreign_key} = ?)", params[:id].to_i]#{', :order=>"'+many_class.scaffold_select_order+'"' if many_class.scaffold_select_order}).collect{|item| [item.scaffold_name, item.id]}
  738. unless #{many_class}.scaffold_use_auto_complete
  739. @items_to_add = #{many_class_name}.find(:all, :conditions=>["#{many_class.primary_key} NOT IN (SELECT #{association_foreign_key} FROM #{join_table} WHERE #{join_table}.#{foreign_key} = ?)", params[:id].to_i]#{', :order=>"'+many_class.scaffold_select_order+'"' if many_class.scaffold_select_order}).collect{|item| [item.scaffold_name, item.id]}
  740. end
  741. @scaffold_update_page = "update#{suffix}"
  742. render_scaffold_template("habtm")
  743. end
  744. def update#{suffix}
  745. flash[:notice] = begin
  746. singular_item = #{singular_name}.find(params[:id])
  747. singular_item.#{many_name}.push(#{many_class_name}.find(multiple_select_ids(params[:add]))) if params[:add] && !params[:add].empty?
  748. singular_item.#{many_name}.delete(#{many_class_name}.find(multiple_select_ids(params[:remove]))) if params[:remove] && !params[:remove].empty?
  749. "Updated #{singular_name}'s #{many_name} successfully"
  750. rescue ::ActiveRecord::StatementInvalid
  751. "Error updating #{singular_name}'s #{many_name}"
  752. end
  753. redirect_to(:action=>"edit#{suffix}", :id=>params[:id])
  754. end
  755. end_eval
  756. both_ways ? scaffold_habtm(many_class, singular_class, false) : true
  757. end
  758. # Scaffolds all models in the Rails app, with all associations. Scaffolds all
  759. # models in the models directory if no arguments are given, otherwise just
  760. # scaffolds for those models.
  761. def scaffold_all_models(*models)
  762. models = ActiveRecord::Base.all_models if models.length == 0
  763. models.each do |model|
  764. scaffold model.to_sym, :suffix=>true, :habtm=>model.to_s.camelize.constantize.reflect_on_all_associations.collect{|r|r.name if r.macro == :has_and_belongs_to_many}.compact
  765. end
  766. module_eval <<-"end_eval", __FILE__, __LINE__
  767. def index
  768. @models = #{models.inspect}
  769. render_scaffold_template("index")
  770. end
  771. end_eval
  772. end
  773. end
  774. end
  775. end