class AutoForme::Action

  1. lib/autoforme/action.rb
Superclass: Object

Represents an action on a model in response to a web request.

Constants

ALL_SUPPORTED_ACTIONS = %w'new create show edit update delete destroy browse search mtm_edit mtm_update association_links autocomplete'.freeze  

Array of strings for all action types currently supported

NON_IDEMPOTENT_TYPES = NORMALIZED_ACTION_MAP = {:create=>:new, :update=>:edit, :destroy=>:delete, :mtm_update=>:mtm_edit}  

Map of regular type symbols to normalized type symbols

TITLE_MAP = {:new=>'New', :show=>'Show', :edit=>'Edit', :delete=>'Delete', :browse=>'Browse', :search=>'Search', :mtm_edit=>'Many To Many Edit'}  

Map of type symbols to HTML titles

Attributes

model [R]

The AutoForme::Model instance related to the current action

normalized_type [R]

The normalized type symbol related to the current action, so that paired actions such as new and create both use :new.

output_filename [R]

The filename to use for file output

output_type [R]

The type of output, currently nil for normal output, and 'csv' for csv output

params_association [R]

An association symbol for the current related association.

request [R]

The AutoForme::Request instance related to the current action

title [R]

An string suitable for use as the HTML title on the displayed page

type [R]

The type symbols related to the current action (e.g. :new, :create).

Public Class methods

new (model, request)

Creates a new action for the model and request. This action is not usable unless supported? is called first and it returns true.

[show source]
   # File lib/autoforme/action.rb
44 def initialize(model, request)
45   @model = model
46   @request = request
47 end

Public Instance methods

base_url_for (page)

A path for the given page tied to the framework, but not the current model. Used for linking to other models in the same framework.

[show source]
    # File lib/autoforme/action.rb
111 def base_url_for(page)
112   "#{request.path}#{model.framework.prefix}/#{page}"
113 end
column_label_for (type, request, model, column)

The label to use for the given column.

[show source]
    # File lib/autoforme/action.rb
174 def column_label_for(type, request, model, column)
175   unless label = model.column_options_for(type, request, column)[:label]
176     label = humanize(column)
177   end
178   label
179 end
column_options_for (type, request, obj, column)

The options to use for the given column, which will be passed to Forme::Form#input.

[show source]
    # File lib/autoforme/action.rb
161 def column_options_for(type, request, obj, column)
162   opts = model.column_options_for(type, request, column)
163   if opts[:class] == 'autoforme_autocomplete'
164     if type == :show
165       opts[:value] = model.column_value(type, request, obj, column)
166     elsif key = obj.send(model.association_key(column))
167       opts[:value] = "#{key} - #{model.column_value(type, request, obj, column)}"
168     end
169   end
170   opts
171 end
csv (meth)

Return a string in CSV format with the results

[show source]
    # File lib/autoforme/action.rb
182 def csv(meth)
183   @output_type = 'csv'
184   @output_filename = "#{model.link.downcase}_#{normalized_type}.csv"
185   columns = model.columns_for(type, request)
186   headers = columns.map{|column| column_label_for(type, request, model, column)}
187   EnumCSV.csv(model.send(meth, normalized_type, request, :all_results=>true), :headers=>headers) do |obj|
188     columns.map{|column| model.column_value(type, request, obj, column)}
189   end
190 end
edit_page (obj)

The page to use when editing the object.

[show source]
    # File lib/autoforme/action.rb
343 def edit_page(obj)
344   page do
345     t = String.new
346     form_attr = form_attributes(:action=>url_for("update/#{model.primary_key_value(obj)}"))
347     t << Forme.form(obj, form_attr, form_opts(form_attr[:action])) do |f|
348       model.columns_for(:edit, request).each do |column|
349         col_opts = column_options_for(:edit, request, obj, column)
350         if html = model.edit_html_for(obj, column, :edit, request)
351           col_opts = col_opts.merge(:html=>html)
352         end
353         f.input(column, col_opts)
354       end
355       f.button(:value=>'Update', :class=>'btn btn-primary')
356     end.to_s
357     if model.supported_action?(:delete, request)
358       t << Forme.form(form_attributes(:action=>url_for("delete/#{model.primary_key_value(obj)}")), form_opts) do |f|
359         f.button(:value=>'Delete', :class=>'btn btn-danger')
360       end.to_s
361     end
362     t << association_links(obj)
363   end
364 end
form_attributes (attrs)

Merge the model's form attributes into the given form attributes, yielding the attributes to use for the form.

[show source]
    # File lib/autoforme/action.rb
239 def form_attributes(attrs)
240   attrs.merge(model.form_attributes_for(type, request))
241 end
form_opts (action=nil)

Options to use for the form. If the form uses POST, automatically adds the CSRF token.

[show source]
    # File lib/autoforme/action.rb
228 def form_opts(action=nil)
229   opts = model.form_options_for(type, request).dup
230   hidden_tags = opts[:hidden_tags] = []
231   if csrf = request.csrf_token_hash(action)
232     hidden_tags << lambda{|tag| csrf if (tag.attr[:method] || tag.attr['method']).to_s.upcase == 'POST'}
233   end
234   opts
235 end
h (s)

Convert input to a string adn HTML escape it.

[show source]
    # File lib/autoforme/action.rb
 99 def h(s)
100   Rack::Utils.escape_html(s.to_s)
101 end
handle ()

Handle the current action, returning an HTML string containing the page content, or redirecting.

[show source]
    # File lib/autoforme/action.rb
149 def handle
150   model.before_action_hook(type, request)
151   send("handle_#{type}")
152 end
handle_autocomplete ()

Handle autocomplete action by returning a string with one line per model object.

[show source]
    # File lib/autoforme/action.rb
542 def handle_autocomplete
543   unless (query = request.params['q'].to_s).empty?
544     model.autocomplete(:type=>@subtype, :request=>request, :association=>params_association, :query=>query, :exclude=>request.params['exclude']).join("\n")
545   end
546 end
handle_browse ()

Handle browse action by showing a table containing model objects.

[show source]
    # File lib/autoforme/action.rb
439 def handle_browse
440   if request.id == 'csv'
441     csv(:browse)
442   else
443     table_page(*model.browse(type, request))
444   end
445 end
handle_create ()

Handle the create action by creating a new model object.

[show source]
    # File lib/autoforme/action.rb
268 def handle_create
269   obj = model.new(nil, request)
270   model.set_fields(obj, :new, request, model_params)
271   model.hook(:before_create, request, obj)
272   if model.save(obj)
273     model.hook(:after_create, request, obj)
274     request.set_flash_notice("Created #{model.class_name}")
275     redirect(:new, obj)
276   else
277     request.set_flash_now_error("Error Creating #{model.class_name}")
278     new_page(obj)
279   end
280 end
handle_delete ()

Handle the edit action by showing a list page if there is no model object selected, or a confirmation screen if there is one.

[show source]
    # File lib/autoforme/action.rb
393 def handle_delete
394   if request.id
395     handle_show
396   else
397     list_page(:delete, :form=>{:action=>url_for('delete')})
398   end
399 end
handle_destroy ()

Handle the destroy action by destroying the model object.

[show source]
    # File lib/autoforme/action.rb
402 def handle_destroy
403   obj = model.with_pk(normalized_type, request, request.id)
404   model.hook(:before_destroy, request, obj)
405   model.destroy(obj)
406   model.hook(:after_destroy, request, obj)
407   request.set_flash_notice("Deleted #{model.class_name}")
408   redirect(:delete, obj)
409 end
handle_edit ()

Handle the edit action by showing a list page if there is no model object selected, or the edit page if there is one.

[show source]
    # File lib/autoforme/action.rb
367 def handle_edit
368   if request.id
369     obj = model.with_pk(normalized_type, request, request.id)
370     model.hook(:before_edit, request, obj)
371     edit_page(obj)
372   else
373     list_page(:edit)
374   end
375 end
handle_mtm_edit ()

Handle the mtm_edit action by showing a list page if there is no model object selected, a list of associations for that model if there is a model object but no association selected, or a mtm_edit form if there is a model object and association selected.

[show source]
    # File lib/autoforme/action.rb
472 def handle_mtm_edit
473   if request.id
474     obj = model.with_pk(:edit, request, request.id)
475     unless assoc = params_association
476       options = model.mtm_association_select_options(request)
477       if options.length == 1
478         assoc = options.first
479       end
480     end
481     if assoc
482       page do
483         t = String.new
484         t << "<h2>Edit #{humanize(assoc)} for #{h model.object_display_name(type, request, obj)}</h2>"
485         form_attr = form_attributes(:action=>url_for("mtm_update/#{model.primary_key_value(obj)}?association=#{assoc}"))
486         t << Forme.form(obj, form_attr, form_opts(form_attr[:action])) do |f|
487           opts = model.column_options_for(:mtm_edit, request, assoc)
488           add_opts = opts[:add] ? opts.merge(opts.delete(:add)) : opts
489           remove_opts = opts[:remove] ? opts.merge(opts.delete(:remove)) : opts
490           add_opts = {:name=>'add[]', :id=>'add', :label=>'Associate With'}.merge(add_opts)
491           if model.association_autocomplete?(assoc, request)
492             f.input(assoc, {:type=>'text', :class=>'autoforme_autocomplete', :attr=>{'data-type'=>'association', 'data-column'=>assoc, 'data-exclude'=>model.primary_key_value(obj)}, :value=>''}.merge(add_opts))
493           else
494             f.input(assoc, {:dataset=>model.unassociated_mtm_objects(request, assoc, obj), :size=>10}.merge(add_opts))
495           end
496           f.input(assoc, {:name=>'remove[]', :id=>'remove', :label=>'Disassociate From', :dataset=>model.associated_mtm_objects(request, assoc, obj), :value=>[], :size=>10}.merge(remove_opts))
497           f.button(:value=>'Update', :class=>'btn btn-primary')
498         end.to_s
499       end
500     else
501       page do
502         Forme.form(form_attributes(:action=>"mtm_edit/#{model.primary_key_value(obj)}"), form_opts) do |f|
503           f.input(:select, :options=>options, :name=>'association', :id=>'association', :label=>'Association', :add_blank=>true)
504           f.button(:value=>'Edit', :class=>'btn btn-primary')
505         end
506       end
507     end
508   else
509     list_page(:edit, :form=>{})
510   end
511 end
handle_mtm_update ()

Handle mtm_update action by updating the related many to many association. For ajax requests, return an HTML fragment to update the page, otherwise redirect to the appropriate form.

[show source]
    # File lib/autoforme/action.rb
515 def handle_mtm_update
516   obj = model.with_pk(:edit, request, request.id)
517   assoc = params_association
518   assoc_obj = model.mtm_update(request, assoc, obj, request.params['add'], request.params['remove'])
519   request.set_flash_notice("Updated #{assoc} association for #{model.class_name}") unless request.xhr?
520   if request.xhr?
521     if request.params['add']
522       @type = :edit
523       mtm_edit_remove(assoc, model.associated_model_class(assoc), obj, assoc_obj)
524     else
525       "<option value=\"#{model.primary_key_value(assoc_obj)}\">#{model.associated_object_display_name(assoc, request, assoc_obj)}</option>"
526     end
527   elsif request.params['redir'] == 'edit'
528     redirect(:edit, obj)
529   else
530     redirect(:mtm_edit, obj)
531   end
532 end
handle_new ()

Handle the new action by always showing the new form.

[show source]
    # File lib/autoforme/action.rb
261 def handle_new
262   obj = model.new(request.params[model.link], request)
263   model.hook(:before_new, request, obj)
264   new_page(obj)
265 end
handle_show ()

Handle the show action by showing a list page if there is no model object selected, or the show page if there is one.

[show source]
    # File lib/autoforme/action.rb
334 def handle_show
335   if request.id
336     show_page(model.with_pk(normalized_type, request, request.id))
337   else
338     list_page(:show)
339   end
340 end
handle_update ()

Handle the update action by updating the current model object.

[show source]
    # File lib/autoforme/action.rb
378 def handle_update
379   obj = model.with_pk(normalized_type, request, request.id)
380   model.set_fields(obj, :edit, request, model_params)
381   model.hook(:before_update, request, obj)
382   if model.save(obj)
383     model.hook(:after_update, request, obj)
384     request.set_flash_notice("Updated #{model.class_name}")
385     redirect(:edit, obj)
386   else
387     request.set_flash_now_error("Error Updating #{model.class_name}")
388     edit_page(obj)
389   end
390 end
humanize (string)

Convert the given object into a suitable human readable string.

[show source]
    # File lib/autoforme/action.rb
155 def humanize(string)
156   string = string.to_s
157   string.respond_to?(:humanize) ? string.humanize : string.gsub(/_/, " ").capitalize
158 end
inline_mtm_edit_forms (obj)

HTML fragment used for the inline mtm_edit forms on the edit page.

[show source]
    # File lib/autoforme/action.rb
640 def inline_mtm_edit_forms(obj)
641   assocs = model.inline_mtm_assocs(request)
642   return if assocs.empty?
643 
644   t = String.new
645   t << "<div class='inline_mtm_add_associations'>"
646   assocs.each do |assoc|
647     form_attr = form_attributes(:action=>url_for("mtm_update/#{model.primary_key_value(obj)}?association=#{assoc}&redir=edit"), :class => 'mtm_add_associations', 'data-remove' => "##{assoc}_remove_list")
648     t << Forme.form(obj, form_attr, form_opts(form_attr[:action])) do |f|
649       opts = model.column_options_for(:mtm_edit, request, assoc)
650       add_opts = opts[:add] ? opts.merge(opts.delete(:add)) : opts.dup
651       add_opts = {:name=>'add[]', :id=>"add_#{assoc}"}.merge(add_opts)
652       if model.association_autocomplete?(assoc, request)
653         f.input(assoc, {:type=>'text', :class=>'autoforme_autocomplete', :attr=>{'data-type'=>'association', 'data-column'=>assoc, 'data-exclude'=>model.primary_key_value(obj)}, :value=>''}.merge(add_opts))
654       else
655         f.input(assoc, {:dataset=>model.unassociated_mtm_objects(request, assoc, obj), :multiple=>false, :add_blank=>true}.merge(add_opts))
656       end
657       f.button(:value=>'Add', :class=>'btn btn-xs btn-primary')
658     end.to_s
659   end
660   t << "</div>"
661   t << "<div class='inline_mtm_remove_associations'><ul>"
662   assocs.each do |assoc|
663     mc = model.associated_model_class(assoc)
664     t << "<li>"
665     t << association_class_link(mc, assoc)
666     t << "<ul id='#{assoc}_remove_list'>"
667     obj.send(assoc).each do |assoc_obj|
668       t << mtm_edit_remove(assoc, mc, obj, assoc_obj)
669     end
670     t << "</ul></li>"
671   end
672   t << "</ul></div>"
673 end
list_page (type, opts={})

Shared page used by show, edit, and delete actions that shows a list of available model objects (or an autocompleting box), and allows the user to choose one to act on.

[show source]
    # File lib/autoforme/action.rb
284 def list_page(type, opts={})
285   page do
286     form_attr = form_attributes(opts[:form] || {:action=>url_for(type)})
287     Forme.form(form_attr, form_opts) do |f|
288       input_opts = {:name=>'id', :id=>'id', :label=>model.class_name}
289       if model.autocomplete_options_for(type, request)
290         input_type = :text
291         input_opts.merge!(:class=>'autoforme_autocomplete', :attr=>{'data-type'=>type})
292       else
293         input_type = :select
294         input_opts.merge!(:options=>model.select_options(type, request), :add_blank=>true)
295       end
296       f.input(input_type, input_opts)
297       f.button(:value=>type.to_s.capitalize, :class=>"btn btn-#{type == :delete ? 'danger' : 'primary'}")
298     end
299   end
300 end
model_params ()

Get request parameters for the model. Used when retrieving form values that use namespacing.

[show source]
    # File lib/autoforme/action.rb
105 def model_params
106   request.params[model.params_name]
107 end
mtm_edit_remove (assoc, mc, obj, assoc_obj)

Line item containing form to remove the currently associated object.

[show source]
    # File lib/autoforme/action.rb
676 def mtm_edit_remove(assoc, mc, obj, assoc_obj)
677   t = String.new
678   t << "<li>"
679   t << association_link(mc, assoc_obj)
680   form_attr = form_attributes(:action=>url_for("mtm_update/#{model.primary_key_value(obj)}?association=#{assoc}&remove%5b%5d=#{model.primary_key_value(assoc_obj)}&redir=edit"), :method=>'post', :class => 'mtm_remove_associations', 'data-add'=>"#add_#{assoc}")
681   t << Forme.form(form_attr, form_opts(form_attr[:action])) do |f|
682     f.button(:value=>'Remove', :class=>'btn btn-xs btn-danger')
683   end.to_s
684   t << "</li>"
685 end
new_page (obj, opts={})

HTML content used for the new action

[show source]
    # File lib/autoforme/action.rb
244 def new_page(obj, opts={})
245   page do
246     form_attr = form_attributes(:action=>url_for("create"))
247     Forme.form(obj, form_attr, form_opts(form_attr[:action])) do |f|
248       model.columns_for(:new, request).each do |column|
249         col_opts = column_options_for(:new, request, obj, column)
250         if html = model.edit_html_for(obj, column, :new, request)
251           col_opts = col_opts.merge(:html=>html)
252         end
253         f.input(column, col_opts)
254       end
255       f.button(:value=>'Create', :class=>'btn btn-primary')
256     end
257   end
258 end
page ()

Yields and wraps the returned data in a header and footer for the page.

[show source]
    # File lib/autoforme/action.rb
217 def page
218   html = String.new
219   html << (model.page_header_for(type, request) || tabs)
220   html << "<div id='autoforme_content' data-url='#{url_for('')}'>"
221   html << yield.to_s
222   html << "</div>"
223   html << model.page_footer_for(type, request).to_s
224   html
225 end
redirect (type, obj)

Redirect to a page based on the type of action and the given object.

[show source]
    # File lib/autoforme/action.rb
126 def redirect(type, obj)
127   if redir = model.redirect_for
128     path = redir.call(obj, type, request)
129   end
130 
131   unless path
132     path = case type
133     when :new, :delete
134       type.to_s
135     when :edit
136       "edit/#{model.primary_key_value(obj)}"
137     when :mtm_edit
138       "mtm_edit/#{model.primary_key_value(obj)}?association=#{params_association}"
139     end
140     path = url_for(path)
141   end
142 
143   request.redirect(path)
144   nil
145 end
show_page (obj)

The page to use when displaying an object, always used as a confirmation screen when deleting an object.

[show source]
    # File lib/autoforme/action.rb
303 def show_page(obj)
304   page do
305     t = String.new
306     f = Forme::Form.new(obj, :formatter=>:readonly, :wrapper=>:trtd, :labeler=>:explicit)
307     t << "<table class=\"#{model.table_class_for(:show, request)}\">"
308     model.columns_for(type, request).each do |column|
309       col_opts = column_options_for(type, request, obj, column)
310       if html = model.show_html_for(obj, column, type, request)
311         col_opts = col_opts.merge(:html=>html)
312       end
313       t << f.input(column, col_opts).to_s
314     end
315     t << '</table>'
316     if type == :show && model.supported_action?(:edit, request)
317       t << Forme.form(form_attributes(:action=>url_for("edit/#{model.primary_key_value(obj)}")), form_opts) do |f1|
318         f1.button(:value=>'Edit', :class=>'btn btn-primary')
319       end.to_s
320     end
321     if type == :delete
322       form_attr = form_attributes(:action=>url_for("destroy/#{model.primary_key_value(obj)}"), :method=>:post)
323       t << Forme.form(form_attr, form_opts(form_attr[:action])) do |f1|
324         f1.button(:value=>'Delete', :class=>'btn btn-danger')
325       end.to_s
326     else
327       t << association_links(obj)
328     end
329     t
330   end
331 end
subtype ()

The subtype of request, used for association_links and autocomplete actions.

[show source]
    # File lib/autoforme/action.rb
121 def subtype
122   ((t = request.params['type']) && ALL_SUPPORTED_ACTIONS.include?(t) && t.to_sym) || :edit
123 end
supported? ()

Return true if the action is supported, and false otherwise. An action may not be supported if the type is not one of the supported types, or if an non-idemponent request is issued with get instead of post, or potentionally other reasons.

As a side-effect, this sets up additional state related to the request.

[show source]
   # File lib/autoforme/action.rb
55 def supported?
56   return false unless ALL_SUPPORTED_ACTIONS.include?(request.action_type)
57   type = @type = request.action_type.to_sym
58   @normalized_type = NORMALIZED_ACTION_MAP.fetch(type, type)
59   return false if NON_IDEMPOTENT_TYPES[type] && !request.post?
60 
61   case type
62   when :mtm_edit
63     return false unless model.supported_action?(type, request)
64     if request.id && (assoc = request.params['association'])
65       return false unless model.supported_mtm_edit?(assoc, request)
66       @params_association = assoc.to_sym
67     end
68 
69     @title = "#{model.class_name} - #{TITLE_MAP[type]}"
70   when :mtm_update
71     return false unless request.id && (assoc = request.params['association']) && model.supported_mtm_update?(assoc, request)
72     @params_association = assoc.to_sym
73   when :association_links
74     @subtype = subtype
75     return false unless model.supported_action?(@subtype, request)
76   when :autocomplete
77     if assoc = request.id
78       return false unless model.association?(assoc)
79       @params_association = assoc.to_sym
80       @subtype = :association
81       return false unless associated_class = model.associated_model_class(@params_association)
82       return false unless associated_class.autocomplete_options_for(@subtype, request)
83     else
84       @subtype = subtype
85       return false unless model.autocomplete_options_for(@subtype, request)
86     end
87   else
88     return false unless model.supported_action?(normalized_type, request)
89 
90     if title = TITLE_MAP[@normalized_type]
91       @title = "#{model.class_name} - #{title}"
92     end
93   end
94 
95   true
96 end
tab_name (type)

The name to give the tab for the given type.

[show source]
    # File lib/autoforme/action.rb
205 def tab_name(type)
206   case type
207   when :browse
208     model.class_name
209   when :mtm_edit
210     'MTM'
211   else
212     type.to_s.capitalize
213   end
214 end
table_page (next_page, objs)

Show page used for browse/search pages.

[show source]
    # File lib/autoforme/action.rb
432 def table_page(next_page, objs)
433   page do
434     Table.new(self, objs).to_s << table_pager(normalized_type, next_page)
435   end
436 end
table_pager (type, next_page)

HTML fragment for the table pager, showing links to next page or previous page for browse/search forms.

[show source]
    # File lib/autoforme/action.rb
412 def table_pager(type, next_page)
413   html = String.new
414   html << '<ul class="pager">'
415   page = request.id.to_i
416   if page > 1
417     html << "<li><a href=\"#{url_for("#{type}/#{page-1}?#{h request.query_string}")}\">Previous</a></li>"
418   else
419     html << '<li class="disabled"><a href="#">Previous</a></li>'
420   end
421   if next_page
422     page = 1 if page < 1
423     html << "<li><a href=\"#{url_for("#{type}/#{page+1}?#{h request.query_string}")}\">Next</a></li>"
424   else
425     html << '<li class="disabled"><a href="#">Next</a></li>'
426   end
427   html << "</ul>"
428   html << "<p><a href=\"#{url_for("#{type}/csv?#{h request.query_string}")}\">CSV Format</a></p>"
429 end
tabs ()

HTML fragment for the default page header, which uses tabs for each supported action.

[show source]
    # File lib/autoforme/action.rb
193 def tabs
194   content = String.new
195   content << '<ul class="nav nav-tabs">'
196   Model::DEFAULT_SUPPORTED_ACTIONS.each do |action_type|
197     if model.supported_action?(action_type, request)
198       content << "<li class=\"#{'active' if type == action_type}\"><a href=\"#{url_for(action_type)}\">#{tab_name(action_type)}</a></li>"
199     end
200   end
201   content << '</ul>'
202 end
url_for (page)

A path for the given page for the same model.

[show source]
    # File lib/autoforme/action.rb
116 def url_for(page)
117   base_url_for("#{model.link}/#{page}")
118 end